Compare commits

...
Author SHA1 Message Date
rkervella b65ee3144e Merge branch 'master' into prelude/stage 2022-09-04 09:24:28 -07:00
rkervella b5c6edffec Merge branch 'master' into prelude/stage 2022-09-01 13:04:49 -07:00
rkervella 9352b4b39c Merge branch 'master' into prelude/stage 2022-09-01 13:02:42 -07:00
rkervella e98b1d5222 Major refactor 2022-08-31 16:07:24 -07:00
rkervella 34c5fe545c Fix call order and add safety check 2022-08-30 14:13:29 -07:00
rkervella cb264f1a4d Add license 2022-08-30 13:54:02 -07:00
rkervella 15763fc2b6 Fix parsing, remove old BOF code 2022-08-30 13:49:02 -07:00
rkervella e959bdc54b Remove deprecated "keyword" executor 2022-08-30 13:30:29 -07:00
rkervella eaae285f4b Reworked extension support for prelude 2022-08-30 12:52:46 -07:00
rkervella 562cc49500 Complete refactor of extensions. 2022-08-30 10:36:13 -07:00
rkervella fff3405d29 Fix name 2022-08-29 11:52:27 -07:00
rkervella 24606aaa8f Remove commented code 2022-08-29 10:16:17 -07:00
rkervella fb5866aba1 Refactor extensions 2022-08-29 09:44:19 -07:00
rkervella 9a411a3290 Refactor extensions 2022-08-29 09:20:04 -07:00
rkervella c29f1c3be6 Merge branch 'master' into prelude/stage 2022-08-29 08:24:25 -07:00
rkervella 7f15c56449 Remove range 2022-08-28 09:20:42 -07:00
rkervella 8fc1f1f00b Remove range, fix command description 2022-08-28 09:19:08 -07:00
Ronan Kervella 4a5ec4afae Merge pull request #805 from BishopFox/prelude/client-daemon
Prelude/client daemon
2022-08-28 09:11:44 -07:00
moloch-- fb5303ef04 Implemented callback map 2022-08-28 08:03:39 -07:00
moloch-- d6a7c024f8 Merge branch 'prelude/stage' into prelude/client-daemon 2022-08-28 07:57:58 -07:00
moloch-- 2adf0e8cb0 Initial refactor 2022-08-27 07:21:53 -07:00
rkervella c2cacb5e1e Merge branch 'master' into prelude/stage 2022-08-26 14:01:37 -07:00
rkervella d7e0293647 WIP extensions 2022-08-25 10:59:49 -07:00
rkervella 82d500ceec Merge branch 'master' into fix/prelude 2022-08-24 14:03:27 -07:00
rkervella a3f5dcd54a WIP prelude update 2022-08-24 11:03:56 -07:00
rkervella 37ff504164 WIP 2022-08-23 17:33:55 -07:00
rkervella 552093c7b6 Fix Windows executor 2022-08-23 14:42:00 -07:00
rkervella 7a143c58e6 Remove useless call to a.GetID() 2022-08-23 14:41:46 -07:00
31 changed files with 1311 additions and 790 deletions

No files matched your search

+1 -2
View File
@@ -26,7 +26,6 @@ import (
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
)
@@ -70,7 +69,7 @@ func GetConfigs() map[string]*ClientConfig {
confs := map[string]*ClientConfig{}
for _, confFile := range configFiles {
confFilePath := path.Join(configDir, confFile.Name())
confFilePath := filepath.Join(configDir, confFile.Name())
log.Printf("Parsing config %s", confFilePath)
conf, err := ReadConfig(confFilePath)
+20 -9
View File
@@ -35,12 +35,32 @@ import (
const (
logFileName = "sliver-client.log"
configFlagStr = "config"
preludeServerFlagStr = "server"
rangeFlagStr = "range"
aesKeyFlagStr = "aes-key"
)
var (
sliverServerVersion = fmt.Sprintf("v%s", version.FullVersion())
)
func init() {
// Import
rootCmd.AddCommand(cmdImport)
// Prelude
cmdPrelude.Flags().StringP(configFlagStr, "c", "", "sliver client config file path")
cmdPrelude.Flags().StringP(preludeServerFlagStr, "s", "", "prelude server connection string")
cmdPrelude.Flags().StringP(aesKeyFlagStr, "a", "", "AES key")
rootCmd.AddCommand(cmdPrelude)
// Version
rootCmd.AddCommand(cmdVersion)
}
// Initialize logging
func initLogging(appDir string) *os.File {
log.SetFlags(log.LstdFlags | log.Lshortfile)
@@ -52,15 +72,6 @@ func initLogging(appDir string) *os.File {
return logFile
}
func init() {
// Import
rootCmd.AddCommand(cmdImport)
// Version
rootCmd.AddCommand(cmdVersion)
}
var rootCmd = &cobra.Command{
Use: "sliver-client",
Short: "",
+124
View File
@@ -0,0 +1,124 @@
package cli
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"fmt"
"log"
"os"
"sync"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/prelude"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/spf13/cobra"
)
var cmdPrelude = &cobra.Command{
Use: "prelude",
Short: "Start client as a Prelude bridge",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
confFilePath, err := cmd.Flags().GetString("config")
if err != nil {
log.Printf("[!] %s", err)
fmt.Printf("%s\n", err)
os.Exit(1)
}
config, err := assets.ReadConfig(confFilePath)
if err != nil {
log.Printf("[!] Failed to read config file: %s", err)
fmt.Printf("%s\n", err)
os.Exit(1)
}
preludeServer, err := cmd.Flags().GetString(preludeServerFlagStr)
if err != nil {
log.Printf("[!] %s", err)
fmt.Printf("%s\n", err)
os.Exit(1)
}
aesKey, err := cmd.Flags().GetString(aesKeyFlagStr)
if err != nil {
log.Printf("[!] %s", err)
fmt.Printf("%s\n", err)
os.Exit(1)
}
fmt.Printf("Prelude relay mode, connecting to %s ... ", preludeServer)
log.Printf("Prelude relay mode, connecting to %s ...", preludeServer)
rpc, _, err := transport.MTLSConnect(config)
if err != nil {
log.Printf("[!] Failed to connect to server %s", err)
fmt.Printf("%s\n", err)
os.Exit(2)
}
fmt.Println("success!")
log.Printf("Connection successful")
preludeConfig := &prelude.OperatorConfig{
OperatorURL: preludeServer,
RPC: rpc,
AESKey: aesKey,
}
implantMapper := prelude.InitImplantMapper(preludeConfig)
sessions, err := rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
log.Printf("[!] Could not get session list: %s", err)
os.Exit(3)
}
if len(sessions.Sessions) > 0 {
log.Printf("Adding existing sessions ...")
for _, session := range sessions.Sessions {
if !session.IsDead {
err = implantMapper.AddImplant(session, nil)
if err != nil {
log.Printf("[!] Could not add session %s to implant mapper: %s", session.Name, err)
}
}
}
log.Printf("Done !")
}
beacons, err := rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
log.Printf("[!] Could not get beacon list: %s", err)
os.Exit(3)
}
if len(beacons.Beacons) > 0 {
log.Printf("Adding existing beacons ...")
// I'm not sure this callback map is actually needed
type BeaconTaskCallback func(*clientpb.BeaconTask)
beaconTaskCallbacks := map[string]BeaconTaskCallback{}
beaconTaskCallbacksMutex := &sync.Mutex{}
for _, beacon := range beacons.Beacons {
err = implantMapper.AddImplant(beacon, func(taskID string, cb func(task *clientpb.BeaconTask)) {
beaconTaskCallbacksMutex.Lock()
defer beaconTaskCallbacksMutex.Unlock()
beaconTaskCallbacks[taskID] = cb
})
if err != nil {
log.Printf("[!] Could not add beacon %s to implant mapper: %s", beacon.Name, err)
}
}
}
},
}
+3 -2
View File
@@ -29,9 +29,10 @@ import (
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
cmdExtensions "github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/extensions"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
@@ -286,7 +287,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
rows := []table.Row{}
for _, pkg := range entries {
color := console.Normal
if extensions.CmdExists(pkg.CommandName, con.App) {
if cmdExtensions.CmdExists(pkg.CommandName, con.App) {
color = console.Green
}
if con.Settings.SmallTermWidth < width {
+8 -7
View File
@@ -28,8 +28,9 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
cmdExtensions "github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/extensions"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
)
@@ -187,7 +188,7 @@ func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTP
deps := make(map[string]struct{})
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
for dep := range deps {
if extensions.CmdExists(dep, con.App) {
if cmdExtensions.CmdExists(dep, con.App) {
continue // Dependency is already installed
}
err := installExtensionPackageByName(dep, clientConfig, con)
@@ -293,17 +294,17 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
con.Printf(console.Clearln + "\r") // Clear download message
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
if installPath == nil {
return errors.New("failed to install extension")
installPath, err := extensions.InstallFromFilePath(tmpFile.Name(), true)
if err != nil {
return err
}
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
if err != nil {
return err
}
if extensions.CmdExists(extCmd.Name, con.App) {
if cmdExtensions.CmdExists(extCmd.Name, con.App) {
con.App.Commands().Remove(extCmd.Name)
}
extensions.ExtensionRegisterCommand(extCmd, con)
cmdExtensions.ExtensionRegisterCommand(extCmd, con)
return nil
}
+1 -1
View File
@@ -22,8 +22,8 @@ import (
"regexp"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/extensions"
"github.com/desertbit/grumble"
)
+1 -1
View File
@@ -24,8 +24,8 @@ import (
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/extensions"
"github.com/desertbit/grumble"
)
+9 -8
View File
@@ -46,7 +46,7 @@ import (
"github.com/bishopfox/sliver/client/command/dllhijack"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
"github.com/bishopfox/sliver/client/command/extensions"
cmdExtensions "github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
@@ -79,6 +79,7 @@ import (
"github.com/bishopfox/sliver/client/command/wireguard"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/extensions"
"github.com/bishopfox/sliver/client/licenses"
"github.com/desertbit/grumble"
)
@@ -127,7 +128,7 @@ func BindCommands(con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to load extension: %s\n", err)
continue
}
extensions.ExtensionRegisterCommand(ext, con)
cmdExtensions.ExtensionRegisterCommand(ext, con)
n++
}
if 0 < n {
@@ -3182,7 +3183,7 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsCmd(ctx, con)
cmdExtensions.ExtensionsCmd(ctx, con)
con.Println()
return nil
},
@@ -3201,7 +3202,7 @@ func BindCommands(con *console.SliverConsoleClient) {
},
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsListCmd(ctx, con)
cmdExtensions.ExtensionsListCmd(ctx, con)
con.Println()
return nil
},
@@ -3214,7 +3215,7 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionLoadCmd(ctx, con)
cmdExtensions.ExtensionLoadCmd(ctx, con)
con.Println()
return nil
},
@@ -3233,7 +3234,7 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsInstallCmd(ctx, con)
cmdExtensions.ExtensionsInstallCmd(ctx, con)
con.Println()
return nil
},
@@ -3252,7 +3253,7 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsRemoveCmd(ctx, con)
cmdExtensions.ExtensionsRemoveCmd(ctx, con)
con.Println()
return nil
},
@@ -3260,7 +3261,7 @@ func BindCommands(con *console.SliverConsoleClient) {
a.String("name", "the command name of the extension to remove")
},
Completer: func(prefix string, args []string) []string {
return extensions.ExtensionsCommandNameCompleter(prefix, args, con)
return cmdExtensions.ExtensionsCommandNameCompleter(prefix, args, con)
},
})
+6 -5
View File
@@ -27,6 +27,7 @@ import (
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/extensions"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
@@ -63,7 +64,7 @@ func PrintExtensions(con *console.SliverConsoleClient) {
})
installedManifests := getInstalledManifests()
for _, extension := range loadedExtensions {
for _, extension := range extensions.GetExtensions() {
installed := ""
if _, ok := installedManifests[extension.CommandName]; ok {
installed = "✅"
@@ -82,7 +83,7 @@ func PrintExtensions(con *console.SliverConsoleClient) {
con.Println(tw.Render())
}
func extensionPlatforms(extension *ExtensionManifest) []string {
func extensionPlatforms(extension *extensions.ExtensionManifest) []string {
platforms := map[string]string{}
for _, entry := range extension.Files {
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
@@ -94,15 +95,15 @@ func extensionPlatforms(extension *ExtensionManifest) []string {
return keys
}
func getInstalledManifests() map[string]*ExtensionManifest {
func getInstalledManifests() map[string]*extensions.ExtensionManifest {
manifestPaths := assets.GetInstalledExtensionManifests()
installedManifests := map[string]*ExtensionManifest{}
installedManifests := map[string]*extensions.ExtensionManifest{}
for _, manifestPath := range manifestPaths {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
continue
}
manifest := &ExtensionManifest{}
manifest := &extensions.ExtensionManifest{}
err = json.Unmarshal(data, manifest)
if err != nil {
continue
+10 -135
View File
@@ -19,20 +19,16 @@ package extensions
*/
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/client/extensions"
"github.com/desertbit/grumble"
)
// ExtensionsInstallCmd - Install an extension
func ExtensionsInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
var installError error
extLocalPath := ctx.Args.String("path")
fi, err := os.Stat(extLocalPath)
if os.IsNotExist(err) {
@@ -40,135 +36,14 @@ func ExtensionsInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient
return
}
if !fi.IsDir() {
InstallFromFilePath(extLocalPath, false, con)
_, installError = extensions.InstallFromFilePath(extLocalPath, false)
} else {
installFromDir(extLocalPath, con)
installError = extensions.InstallFromDir(extLocalPath)
}
if installError != nil {
con.PrintErrorf("Error installing extension: %s\n", installError)
return
} else {
con.PrintInfof("Extension installed\n")
}
}
// Install an extension from a directory
func installFromDir(extLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := ioutil.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
return
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Extension '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nError creating extension directory: %s\n", err)
return
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
forceRemoveAll(installPath)
return
}
}
}
}
// InstallFromFilePath - Install an extension from a .tar.gz file
func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err)
return nil
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
return nil
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil
}
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nFailed to create extension directory: %s\n", err)
return nil
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return nil
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
if err != nil {
con.PrintErrorf("\nFailed to install file: %s\n", err)
forceRemoveAll(installPath)
return nil
}
}
}
con.Printf("done!\n")
return &installPath
}
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(extGzFilePath, "."+artifactPath)
if err != nil {
return err
}
if len(data) == 0 {
return fmt.Errorf("archive path '%s' is empty", "."+artifactPath)
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
err := os.MkdirAll(artifactDir, 0o700)
if err != nil {
return err
}
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
+16 -206
View File
@@ -21,90 +21,30 @@ package extensions
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/assets"
"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/core"
"github.com/bishopfox/sliver/client/extensions"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
const (
defaultTimeout = 60
// ManifestFileName - Extension manifest file name
ManifestFileName = "extension.json"
)
var loadedExtensions = map[string]*ExtensionManifest{}
type ExtensionManifest struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
Version string `json:"version"`
ExtensionAuthor string `json:"extension_author"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
LongHelp string `json:"long_help"`
Files []*extensionFile `json:"files"`
Arguments []*extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"depends_on"`
Init string `json:"init"`
RootPath string `json:"-"`
}
type extensionFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type extensionArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
}
func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
filePath = path.Join(assets.GetExtensionsDir(), e.CommandName, extFile.Path)
break
}
}
if filePath == "" {
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
return "", err
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
err = fmt.Errorf("extension file not found: %s", filePath)
return "", err
}
return filePath, nil
}
// ExtensionLoadCmd - Load extension command
func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(dirPath, extensions.ManifestFileName))
if err != nil {
return
}
@@ -117,60 +57,9 @@ func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
}
// LoadExtensionManifest - Parse extension files
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extManifest, err := ParseExtensionManifest(data)
if err != nil {
return nil, err
}
extManifest.RootPath = filepath.Dir(manifestPath)
loadedExtensions[extManifest.CommandName] = extManifest
return extManifest, nil
}
// ParseExtensionManifest - Parse extension manifest from buffer
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
extManifest := &ExtensionManifest{}
err := json.Unmarshal(data, &extManifest)
if err != nil {
return nil, err
}
if extManifest.Name == "" {
return nil, errors.New("missing `name` field in extension manifest")
}
if extManifest.CommandName == "" {
return nil, errors.New("missing `command_name` field in extension manifest")
}
if len(extManifest.Files) == 0 {
return nil, errors.New("missing `files` field in extension manifest")
}
for _, extFiles := range extManifest.Files {
if extFiles.OS == "" {
return nil, errors.New("missing `files.os` field in extension manifest")
}
if extFiles.Arch == "" {
return nil, errors.New("missing `files.arch` field in extension manifest")
}
extFiles.Path = util.ResolvePath(extFiles.Path)
if extFiles.Path == "" || extFiles.Path == "/" {
return nil, errors.New("missing `files.path` field in extension manifest")
}
extFiles.OS = strings.ToLower(extFiles.OS)
extFiles.Arch = strings.ToLower(extFiles.Arch)
}
if extManifest.Help == "" {
return nil, errors.New("missing `help` field in extension manifest")
}
return extManifest, nil
}
// ExtensionRegisterCommand - Register a new extension command
func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverConsoleClient) {
loadedExtensions[extCmd.CommandName] = extCmd
func ExtensionRegisterCommand(extCmd *extensions.ExtensionManifest, con *console.SliverConsoleClient) {
extensions.AddExtension(extCmd)
helpMsg := extCmd.Help
extensionCmd := &grumble.Command{
Name: extCmd.CommandName,
@@ -220,90 +109,6 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
con.App.AddCommand(extensionCmd)
}
func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionManifest, ctx *grumble.Context, con *console.SliverConsoleClient) error {
var extensionList []string
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
if err != nil {
return err
}
// Try to find the extension in the loaded extensions
if checkCache {
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("List extensions error: %s\n", err.Error())
return err
}
if extList.Response != nil && extList.Response.Err != "" {
return errors.New(extList.Response.Err)
}
extensionList = extList.Names
}
depLoaded := false
for _, extName := range extensionList {
if !depLoaded && extName == ext.DependsOn {
depLoaded = true
}
if ext.CommandName == extName {
return nil
}
}
// Extension not found, let's load it
if filepath.Ext(binPath) == ".o" {
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
// Auto load the coff loader if we have it
if !depLoaded {
if errLoad := loadDep(goos, goarch, ext.DependsOn, ctx, con); errLoad != nil {
return errLoad
}
}
return nil
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
return err
}
if errRegister := registerExtension(goos, ext, binData, ctx, con); errRegister != nil {
return errRegister
}
return nil
}
func registerExtension(goos string, ext *ExtensionManifest, binData []byte, ctx *grumble.Context, con *console.SliverConsoleClient) error {
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: ext.CommandName,
Data: binData,
OS: goos,
Init: ext.Init,
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
return err
}
if registerResp.Response != nil && registerResp.Response.Err != "" {
return errors.New(registerResp.Response.Err)
}
return nil
}
func loadDep(goos string, goarch string, depName string, ctx *grumble.Context, con *console.SliverConsoleClient) error {
depExt, ok := loadedExtensions[depName]
if ok {
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, goos, goarch)
if err != nil {
return err
}
depBinData, err := ioutil.ReadFile(depBinPath)
if err != nil {
return err
}
return registerExtension(goos, depExt, depBinData, ctx, con)
}
return fmt.Errorf("missing dependency %s", depName)
}
func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
var (
err error
@@ -325,19 +130,19 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
goarch = beacon.Arch
}
ext, ok := loadedExtensions[ctx.Command.Name]
if !ok {
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
ext, err := extensions.GetLoadedExtension(ctx.Command.Name)
if err != nil {
con.PrintErrorf("%v\n", err)
return
}
checkCache := session != nil
if err = loadExtension(goos, goarch, checkCache, ext, ctx, con); err != nil {
if err = extensions.LoadExtension(goos, goarch, checkCache, ext, con.ActiveTarget.Request(ctx), con.Rpc); err != nil {
con.PrintErrorf("Could not load extension: %s\n", err)
return
}
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
binPath, err := ext.GetFileForTarget(ctx.Command.Name, goos, goarch)
if err != nil {
con.PrintErrorf("Failed to read extension file: %s\n", err)
return
@@ -358,7 +163,12 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
extName = ext.DependsOn
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
depExt, err := extensions.GetLoadedExtension(extName)
if err != nil {
con.PrintErrorf("Could not find dependency %s for extension %s\n", extName, ext.CommandName)
return
}
entryPoint = depExt.Entrypoint // should exist at this point
} else {
// Regular DLL
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
@@ -414,7 +224,7 @@ func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.
}
}
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
func getBOFArgs(ctx *grumble.Context, binPath string, ext *extensions.ExtensionManifest) ([]byte, error) {
var extensionArgs []byte
binData, err := ioutil.ReadFile(binPath)
if err != nil {
+3 -29
View File
@@ -19,15 +19,11 @@ package extensions
*/
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/client/extensions"
"github.com/desertbit/grumble"
)
@@ -44,7 +40,8 @@ func ExtensionsRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
if !confirm {
return
}
err := RemoveExtensionByCommandName(name, con)
con.App.Commands().Remove(name)
err := extensions.RemoveExtensionByCommandName(name)
if err != nil {
con.PrintErrorf("Error removing extension: %s\n", err)
return
@@ -52,26 +49,3 @@ func ExtensionsRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
con.PrintInfof("Extension '%s' removed\n", name)
}
}
// RemoveExtensionByCommandName - Remove an extension by command name
func RemoveExtensionByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedExtensions[commandName]; !ok {
return errors.New("extension not loaded")
}
delete(loadedExtensions, commandName)
con.App.Commands().Remove(commandName)
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
forceRemoveAll(extPath)
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
+103
View File
@@ -0,0 +1,103 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"os"
"path"
"github.com/bishopfox/sliver/client/assets"
)
const (
// ManifestFileName - Extension manifest file name
ManifestFileName = "extension.json"
)
var loadedExtensions = map[string]*ExtensionManifest{}
type ExtensionManifest struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
Version string `json:"version"`
ExtensionAuthor string `json:"extension_author"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
LongHelp string `json:"long_help"`
Files []*extensionFile `json:"files"`
Arguments []*extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"depends_on"`
Init string `json:"init"`
RootPath string `json:"-"`
}
type extensionFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type extensionArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
}
func AddExtension(ext *ExtensionManifest) {
loadedExtensions[ext.CommandName] = ext
}
func GetLoadedExtension(name string) (*ExtensionManifest, error) {
if ext, ok := loadedExtensions[name]; ok {
return ext, nil
}
return nil, fmt.Errorf("extension not found: %s", name)
}
func GetExtensions() []*ExtensionManifest {
extensions := []*ExtensionManifest{}
for _, ext := range loadedExtensions {
extensions = append(extensions, ext)
}
return extensions
}
func (e *ExtensionManifest) GetFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
filePath = path.Join(assets.GetExtensionsDir(), e.CommandName, extFile.Path)
break
}
}
if filePath == "" {
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
return "", err
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
err = fmt.Errorf("extension file not found: %s", filePath)
return "", err
}
return filePath, nil
}
+145
View File
@@ -0,0 +1,145 @@
package extensions
/*
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 (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/util"
)
// Install an extension from a directory
func InstallFromDir(extLocalPath string) error {
manifestData, err := ioutil.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
if err != nil {
return fmt.Errorf("error reading %s: %s", ManifestFileName, err)
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
return fmt.Errorf("error parsing %s: %s", ManifestFileName, err)
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
confirm := false
msg := fmt.Sprintf("Extension '%s' already exists. Overwrite current install?", manifest.CommandName)
prompt := &survey.Confirm{Message: msg}
survey.AskOne(prompt, &confirm)
if !confirm {
return err
}
forceRemoveAll(installPath)
}
err = os.MkdirAll(installPath, 0o700)
if err != nil {
return fmt.Errorf("error creating extension directory: %s", err)
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
forceRemoveAll(installPath)
return fmt.Errorf("error writing %s: %s", ManifestFileName, err)
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
forceRemoveAll(installPath)
return fmt.Errorf("error copying file '%s' -> '%s': %s", src, dst, err)
}
}
}
return nil
}
// InstallFromFilePath - Install an extension from a .tar.gz file
func InstallFromFilePath(extLocalPath string, autoOverwrite bool) (*string, error) {
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
return nil, fmt.Errorf("failed to read %s from '%s': %s", ManifestFileName, extLocalPath, err)
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %s", ManifestFileName, err)
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
confirm := false
msg := fmt.Sprintf("Extension '%s' already exists. Overwrite current install?", manifest.CommandName)
prompt := &survey.Confirm{Message: msg}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil, err
}
}
forceRemoveAll(installPath)
}
// con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
return nil, fmt.Errorf("failed to create extension directory: %s", err)
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
forceRemoveAll(installPath)
return nil, fmt.Errorf("failed to write %s: %s", ManifestFileName, err)
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
err = installArtifact(extLocalPath, installPath, manifestFile.Path)
if err != nil {
forceRemoveAll(installPath)
return nil, fmt.Errorf("failed to install file: %s", err)
}
}
}
return &installPath, err
}
func installArtifact(extGzFilePath string, installPath string, artifactPath string) error {
data, err := util.ReadFileFromTarGz(extGzFilePath, "."+artifactPath)
if err != nil {
return err
}
if len(data) == 0 {
return fmt.Errorf("archive path '%s' is empty", "."+artifactPath)
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
err := os.MkdirAll(artifactDir, 0o700)
if err != nil {
return err
}
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
+168
View File
@@ -0,0 +1,168 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
)
// ParseExtensionManifest - Parse extension manifest from buffer
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
extManifest := &ExtensionManifest{}
err := json.Unmarshal(data, &extManifest)
if err != nil {
return nil, err
}
if extManifest.Name == "" {
return nil, errors.New("missing `name` field in extension manifest")
}
if extManifest.CommandName == "" {
return nil, errors.New("missing `command_name` field in extension manifest")
}
if len(extManifest.Files) == 0 {
return nil, errors.New("missing `files` field in extension manifest")
}
for _, extFiles := range extManifest.Files {
if extFiles.OS == "" {
return nil, errors.New("missing `files.os` field in extension manifest")
}
if extFiles.Arch == "" {
return nil, errors.New("missing `files.arch` field in extension manifest")
}
extFiles.Path = util.ResolvePath(extFiles.Path)
if extFiles.Path == "" || extFiles.Path == "/" {
return nil, errors.New("missing `files.path` field in extension manifest")
}
extFiles.OS = strings.ToLower(extFiles.OS)
extFiles.Arch = strings.ToLower(extFiles.Arch)
}
if extManifest.Help == "" {
return nil, errors.New("missing `help` field in extension manifest")
}
return extManifest, nil
}
// LoadExtensionManifest - Parse extension files
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extManifest, err := ParseExtensionManifest(data)
if err != nil {
return nil, err
}
extManifest.RootPath = filepath.Dir(manifestPath)
loadedExtensions[extManifest.CommandName] = extManifest
return extManifest, nil
}
func LoadExtension(goos string, goarch string, checkCache bool, ext *ExtensionManifest, request *commonpb.Request, rpc rpcpb.SliverRPCClient) error {
var extensionList []string
binPath, err := ext.GetFileForTarget(ext.CommandName, goos, goarch)
if err != nil {
return err
}
// Try to find the extension in the loaded extensions
if checkCache {
extList, err := rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: request,
})
if err != nil {
return err
}
if extList.Response != nil && extList.Response.Err != "" {
return errors.New(extList.Response.Err)
}
extensionList = extList.Names
}
depLoaded := false
for _, extName := range extensionList {
if !depLoaded && extName == ext.DependsOn {
depLoaded = true
}
if ext.CommandName == extName {
return nil
}
}
// Extension not found, let's load it
if filepath.Ext(binPath) == ".o" {
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
// Auto load the coff loader if we have it
if !depLoaded {
if errLoad := loadDep(goos, goarch, ext.DependsOn, request, rpc); errLoad != nil {
return errLoad
}
}
return nil
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
return err
}
if errRegister := registerExtension(goos, ext, binData, request, rpc); errRegister != nil {
return errRegister
}
return nil
}
func registerExtension(goos string, ext *ExtensionManifest, binData []byte, request *commonpb.Request, rpc rpcpb.SliverRPCClient) error {
registerResp, err := rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: ext.CommandName,
Data: binData,
OS: goos,
Init: ext.Init,
Request: request,
})
if err != nil {
return err
}
if registerResp.Response != nil && registerResp.Response.Err != "" {
return errors.New(registerResp.Response.Err)
}
return nil
}
func loadDep(goos string, goarch string, depName string, request *commonpb.Request, rpc rpcpb.SliverRPCClient) error {
depExt, ok := loadedExtensions[depName]
if ok {
depBinPath, err := depExt.GetFileForTarget(depExt.CommandName, goos, goarch)
if err != nil {
return err
}
depBinData, err := ioutil.ReadFile(depBinPath)
if err != nil {
return err
}
return registerExtension(goos, depExt, depBinData, request, rpc)
}
return fmt.Errorf("missing dependency %s", depName)
}
+50
View File
@@ -0,0 +1,50 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"errors"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/util"
)
// RemoveExtensionByCommandName - Remove an extension by command name
func RemoveExtensionByCommandName(commandName string) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedExtensions[commandName]; !ok {
return errors.New("extension not loaded")
}
delete(loadedExtensions, commandName)
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
forceRemoveAll(extPath)
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
+9 -1
View File
@@ -1,4 +1,12 @@
Prelude
=======
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
## BOF support
To execute BOF TTPs, use the custom `bof` executor:
```json
bof.[{"type": "int", "value": #{json.T1234.ProcessId}}]
```
-175
View File
@@ -1,175 +0,0 @@
package prelude
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"context"
"errors"
"io/ioutil"
"path"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
const (
coffLoaderName = "coff-loader"
loaderEntryPoint = "LoadAndRun"
bofEntryPoint = "go"
)
type bofArgs struct {
ArgType string `json:"type"`
Value interface{} `json:"value"`
}
func runBOF(implant ActiveImplant, rpc rpcpb.SliverRPCClient, bof []byte, args []bofArgs, onFinishCallback func(string, int, int)) (output string, err error) {
if !isLoaderLoaded(implant, rpc) {
err = registerLoader(implant, rpc)
if err != nil {
return
}
}
bofArgs := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
for _, a := range args {
switch a.ArgType {
case "integer":
fallthrough
case "int":
if v, ok := a.Value.(float64); ok {
err = bofArgs.AddInt(uint32(v))
}
case "string":
if v, ok := a.Value.(string); ok {
err = bofArgs.AddString(v)
}
case "wstring":
if v, ok := a.Value.(string); ok {
err = bofArgs.AddWString(v)
}
case "short":
if v, ok := a.Value.(float64); ok {
err = bofArgs.AddShort(uint16(v))
}
}
if err != nil {
return
}
}
extArgs := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
parsedArgs, err := bofArgs.GetBuffer()
if err != nil {
return
}
err = extArgs.AddString(bofEntryPoint)
if err != nil {
return
}
err = extArgs.AddData(bof)
if err != nil {
return
}
err = extArgs.AddData(parsedArgs)
if err != nil {
return
}
extArgsBuffer, err := extArgs.GetBuffer()
if err != nil {
return
}
extResp, err := rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
Name: coffLoaderName,
ServerStore: false,
Args: extArgsBuffer,
Export: loaderEntryPoint,
Request: MakeRequest(implant),
})
if err != nil {
return
}
// If Async req, onFinishCallback won't be nil
if onFinishCallback != nil {
onFinishCallback(string(extResp.Output), SuccessExitStatus, int(implant.GetPID()))
return
}
if extResp.Response != nil && extResp.Response.Err != "" {
err = errors.New(extResp.Response.Err)
}
output = string(extResp.Output)
return
}
func registerLoader(implant ActiveImplant, rpc rpcpb.SliverRPCClient) error {
var coffLoaderPath string
switch implant.GetArch() {
case "amd64":
coffLoaderPath = "COFFLoader.x64.dll"
case "386":
coffLoaderPath = "COFFLoader.x86.dll"
}
loaderPath := path.Join(assets.GetExtensionsDir(), coffLoaderName, coffLoaderPath)
loaderData, err := ioutil.ReadFile(loaderPath)
if err != nil {
return err
}
resp, err := rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: coffLoaderName,
Data: loaderData,
OS: implant.GetOS(),
Init: "",
Request: MakeRequest(implant),
})
if err != nil {
return err
}
if resp.Response != nil && resp.Response.Err != "" {
return errors.New(resp.Response.Err)
}
return nil
}
func isLoaderLoaded(implant ActiveImplant, rpc rpcpb.SliverRPCClient) bool {
extList, err := rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: MakeRequest(implant),
})
if err != nil {
return false
}
for _, ext := range extList.Names {
if ext == coffLoaderName {
return true
}
}
return false
}
@@ -1,4 +1,4 @@
package prelude
package bridge
/*
Sliver Implant Framework
@@ -30,33 +30,36 @@ import (
"strings"
"time"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/implant"
"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/util"
)
const (
defaultTimeout = 60
delim = "\r\n"
delim = "\r\n"
)
type CommandRunner func(string, string, []byte, *OperatorImplantBridge, func(string, int, int)) (string, int, int)
// OperatorImplantBridge maps the Sliver implants (whether it be a beacon or a session)
// to a Prelude Operator implant with a TCP transport.
type OperatorImplantBridge struct {
Conn *net.Conn
Implant ActiveImplant
Implant implant.ActiveImplant
RPC rpcpb.SliverRPCClient
PBeacon OperatorBeacon
PBeacon config.OperatorBeacon
BeaconCallback func(string, func(*clientpb.BeaconTask))
Config AgentConfig
Config config.AgentConfig
RunCommand CommandRunner
recv chan []byte
send chan []byte
}
func NewImplantBridge(c *net.Conn, a ActiveImplant, rpc rpcpb.SliverRPCClient, pbeacon OperatorBeacon, conf AgentConfig, callback func(string, func(*clientpb.BeaconTask))) *OperatorImplantBridge {
func NewImplantBridge(c *net.Conn, a implant.ActiveImplant, rpc rpcpb.SliverRPCClient, pbeacon config.OperatorBeacon, conf config.AgentConfig, callback func(string, func(*clientpb.BeaconTask)), runner CommandRunner) *OperatorImplantBridge {
return &OperatorImplantBridge{
Conn: c,
Implant: a,
@@ -64,6 +67,7 @@ func NewImplantBridge(c *net.Conn, a ActiveImplant, rpc rpcpb.SliverRPCClient, p
Config: conf,
BeaconCallback: callback,
PBeacon: pbeacon,
RunCommand: runner,
recv: make(chan []byte),
send: make(chan []byte),
}
@@ -100,7 +104,7 @@ func (a *OperatorImplantBridge) ReceiveLoop() {
}
func (a *OperatorImplantBridge) handleMessage(message string) {
var tempBeacon OperatorBeacon
var tempBeacon config.OperatorBeacon
decoded, err := hex.DecodeString(message)
if err != nil {
return
@@ -111,7 +115,7 @@ func (a *OperatorImplantBridge) handleMessage(message string) {
}
}
func (implantBridge *OperatorImplantBridge) runLinks(tempBeacon *OperatorBeacon) {
func (implantBridge *OperatorImplantBridge) runLinks(tempBeacon *config.OperatorBeacon) {
for _, link := range implantBridge.Config.StartInstructions(tempBeacon.Links) {
time.Sleep(time.Second * 1)
var payload []byte
@@ -121,7 +125,7 @@ func (implantBridge *OperatorImplantBridge) runLinks(tempBeacon *OperatorBeacon)
// If we're running on a Beacon
if implantBridge.BeaconCallback != nil {
RunCommand(link.Request, link.Executor, payload, implantBridge, func(response string, status int, pid int) {
implantBridge.RunCommand(link.Request, link.Executor, payload, implantBridge, func(response string, status int, pid int) {
link.Response = response
link.Status = status
link.Pid = pid
@@ -137,7 +141,7 @@ func (implantBridge *OperatorImplantBridge) runLinks(tempBeacon *OperatorBeacon)
return
}
// Running on a Session
response, status, pid := RunCommand(link.Request, link.Executor, payload, implantBridge, nil)
response, status, pid := implantBridge.RunCommand(link.Request, link.Executor, payload, implantBridge, nil)
link.Response = response
link.Status = status
link.Pid = pid
@@ -167,7 +171,7 @@ func requestPayload(target string) ([]byte, error) {
func (a *OperatorImplantBridge) refreshBeacon() {
var pwd string
pwdResp, _ := a.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
Request: MakeRequest(a.Implant),
Request: implant.MakeRequest(a.Implant),
})
if pwdResp != nil {
pwd = pwdResp.Path
@@ -179,28 +183,3 @@ func (a *OperatorImplantBridge) refreshBeacon() {
a.PBeacon.Executing = a.Config.BuildExecutingHash()
}
func MakeRequest(a ActiveImplant) *commonpb.Request {
timeout := int64(defaultTimeout)
req := &commonpb.Request{
Timeout: timeout,
}
if a == nil {
return nil
}
beacon, ok := a.(*clientpb.Beacon)
if ok {
req.BeaconID = beacon.ID
req.Async = true
return req
}
session, ok := a.(*clientpb.Session)
if ok {
req.SessionID = session.ID
req.Async = false
return req
}
return nil
}
+53 -70
View File
@@ -24,66 +24,71 @@ import (
"fmt"
"strings"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/executor"
"github.com/bishopfox/sliver/client/prelude/implant"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
// Exxpected message looks like:
// Ls:
// {
// "Command": "ls",
// "Arguments": {
// "Path": "/"
// }
// "Output": "json"
// }
// Extension (BOF):
// {
// "Command": "run-extension",
// "Arguments": {
// "Name": "sa-cacls",
// "Arguments": [{"type":"wstring","value":"/"}]
// }
// }
// Extension (shared lib):
// {
// "Command": "run-extension",
// "Arguments": {
// "Name": "proc-dump",
// "Arguments": ["4343"]
// }
// }
type sliverExecutorMessage struct {
Command string
Output string // "text" or "json", default is "text"
Arguments interface{}
}
//RunCommand executes a given command
func RunCommand(message string, executor string, payload []byte, agentSession *OperatorImplantBridge, onFinish func(string, int, int)) (string, int, int) {
func RunCommand(message string, executor string, payload []byte, agentSession *bridge.OperatorImplantBridge, onFinish func(string, int, int)) (string, int, int) {
switch executor {
case "keyword":
task := splitMessage(message, '.')
switch task[0] {
case "bof":
if len(task) != 2 {
break
}
var bargs []bofArgs
argStr := strings.ReplaceAll(task[1], `\`, `\\`)
err := json.Unmarshal([]byte(argStr), &bargs)
if err != nil {
return err.Error(), ErrorExitStatus, ErrorExitStatus
}
if payload == nil {
return "missing BOF file", ErrorExitStatus, ErrorExitStatus
}
out, err := runBOF(agentSession.Implant, agentSession.RPC, payload, bargs, onFinish)
if err != nil {
return err.Error(), ErrorExitStatus, ErrorExitStatus
}
return out, 0, 0
case "execute-assembly":
if len(task) < 2 {
break
}
var eaargs execasmArgs
argStr := strings.ReplaceAll(task[1], `\`, `\\`)
err := json.Unmarshal([]byte(argStr), &eaargs)
if err != nil {
return err.Error(), ErrorExitStatus, ErrorExitStatus
}
if payload == nil {
return "missing .NET assembly", ErrorExitStatus, ErrorExitStatus
}
out, err := execAsm(agentSession.Implant, agentSession.RPC, payload, eaargs, onFinish)
if err != nil {
return err.Error(), ErrorExitStatus, ErrorExitStatus
}
return out, 0, 0
case "exit":
return shutdown(agentSession)
default:
return "Keyword selected not available for agent", ErrorExitStatus, ErrorExitStatus
case "sliver":
sliverMsg := &sliverExecutorMessage{}
err := json.Unmarshal([]byte(message), sliverMsg)
if err != nil {
return fmt.Sprintf("Error: %s\n", err.Error()), config.ErrorExitStatus, config.ErrorExitStatus
}
return runSliverExecutor(sliverMsg, payload, agentSession, onFinish)
default:
bites, status, pid := execute(message, executor, agentSession, onFinish)
return string(bites), status, pid
}
return "", ErrorExitStatus, ErrorExitStatus
}
func execute(cmd string, executor string, implantBridge *OperatorImplantBridge, onFinishCallback func(string, int, int)) (string, int, int) {
func runSliverExecutor(msg *sliverExecutorMessage, payload []byte, impBridge *bridge.OperatorImplantBridge, onFinish func(string, int, int)) (string, int, int) {
handler := executor.GetHandler(msg.Command)
if handler != nil {
return handler(msg.Arguments, payload, impBridge, onFinish, msg.Output)
}
return "Unknown command", config.ErrorExitStatus, config.ErrorExitStatus
}
func execute(cmd string, executor string, implantBridge *bridge.OperatorImplantBridge, onFinishCallback func(string, int, int)) (string, int, int) {
args := append(getCmdArg(executor), cmd)
if executor == "psh" {
executor = "powershell.exe"
@@ -96,7 +101,7 @@ func execute(cmd string, executor string, implantBridge *OperatorImplantBridge,
Path: executor,
Args: args,
Output: true,
Request: MakeRequest(implantBridge.Implant),
Request: implant.MakeRequest(implantBridge.Implant),
})
if err != nil {
@@ -117,7 +122,7 @@ func execute(cmd string, executor string, implantBridge *OperatorImplantBridge,
// Session
if execResp.Response != nil && execResp.Response.Err != "" {
return execResp.Response.Err, SuccessExitStatus, SuccessExitStatus
return execResp.Response.Err, config.SuccessExitStatus, config.SuccessExitStatus
}
return string(execResp.Stdout), int(execResp.Status), int(execResp.Pid)
}
@@ -136,25 +141,3 @@ func getCmdArg(executor string) []string {
}
return args
}
func splitMessage(message string, splitRune rune) []string {
quoted := false
values := strings.FieldsFunc(message, func(r rune) bool {
if r == '"' {
quoted = !quoted
}
return !quoted && r == splitRune
})
return values
}
func shutdown(agentSession *OperatorImplantBridge) (string, int, int) {
_, err := agentSession.RPC.Kill(context.Background(), &sliverpb.KillReq{
Force: false,
Request: MakeRequest(agentSession.Implant),
})
if err != nil {
return err.Error(), ErrorExitStatus, ErrorExitStatus
}
return fmt.Sprintf("Terminated %s", agentSession.Implant.GetID()), SuccessExitStatus, int(agentSession.Implant.GetPID())
}
@@ -1,4 +1,4 @@
package prelude
package config
/*
Sliver Implant Framework
-68
View File
@@ -1,68 +0,0 @@
package prelude
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"errors"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
type execasmArgs struct {
IsDLL bool `json:"isDLL"`
Process string `json:"process"`
Arguments string `json:"arguments"`
Architecture string `json:"architecture"`
Method string `json:"method"`
Class string `json:"class"`
AppDomain string `json:"appDomain"`
}
func execAsm(implant ActiveImplant, rpc rpcpb.SliverRPCClient, asm []byte, args execasmArgs, onFinishCallback func(string, int, int)) (output string, err error) {
extResp, err := rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: MakeRequest(implant),
IsDLL: args.IsDLL,
Process: args.Process,
Arguments: args.Arguments,
Assembly: asm,
Arch: args.Architecture,
Method: args.Method,
ClassName: args.Class,
AppDomain: args.AppDomain,
})
if err != nil {
return
}
// If Async req, onFinishCallback won't be nil
if onFinishCallback != nil {
onFinishCallback(string(extResp.Output), SuccessExitStatus, int(implant.GetPID()))
return
}
if extResp.Response != nil && extResp.Response.Err != "" {
err = errors.New(extResp.Response.Err)
}
output = string(extResp.Output)
return
}
+106
View File
@@ -0,0 +1,106 @@
package executor
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"encoding/json"
"errors"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/implant"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
type execasmArgs struct {
IsDLL bool `json:"isDLL"`
Process string `json:"process"`
Arguments string `json:"arguments"`
Architecture string `json:"architecture"`
Method string `json:"method"`
Class string `json:"class"`
AppDomain string `json:"appDomain"`
Runtime string `json:"runtime"`
InProcess bool `json:"inProcess"`
PPid int32 `json:"ppid"`
ProcessArgs string `json:"processArgs"`
EtwBypass bool `json:"etwBypass"`
AmsiBypass bool `json:"amsiBypass"`
}
func executeAssemblyHandler(arguments interface{}, payload []byte, impBridge *bridge.OperatorImplantBridge, cb func(string, int, int), outputFormat string) (string, int, int) {
argsByte, err := json.Marshal(arguments)
if err != nil {
return sendError(err)
}
asmArgs := execasmArgs{}
err = json.Unmarshal(argsByte, &asmArgs)
if err != nil {
return sendError(err)
}
extResp, err := impBridge.RPC.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: implant.MakeRequest(impBridge.Implant),
IsDLL: asmArgs.IsDLL,
Process: asmArgs.Process,
Arguments: asmArgs.Arguments,
Assembly: payload,
Arch: asmArgs.Architecture,
Method: asmArgs.Method,
ClassName: asmArgs.Class,
AppDomain: asmArgs.AppDomain,
AmsiBypass: asmArgs.AmsiBypass,
EtwBypass: asmArgs.EtwBypass,
Runtime: asmArgs.Runtime,
InProcess: asmArgs.InProcess,
PPid: uint32(asmArgs.PPid),
})
if err != nil {
return sendError(err)
}
// Async callback
if extResp.Response != nil && extResp.Response.Async {
impBridge.BeaconCallback(extResp.Response.TaskID, func(task *clientpb.BeaconTask) {
err := proto.Unmarshal(task.Response, extResp)
if err != nil {
cb(sendError(err))
return
}
cb(handleExecAsmOutput(extResp, int(impBridge.Implant.GetPID()), outputFormat))
})
return "", config.SuccessExitStatus, int(impBridge.Implant.GetPID())
}
// Sync response
if extResp.Response != nil && extResp.Response.Err != "" {
return sendError(errors.New(extResp.Response.Err))
}
return handleExecAsmOutput(extResp, int(asmArgs.PPid), outputFormat)
}
func handleExecAsmOutput(asmResp *sliverpb.ExecuteAssembly, pid int, format string) (string, int, int) {
if format == "json" {
return JSONFormatter(asmResp, pid)
}
return string(asmResp.Output), config.SuccessExitStatus, pid
}
+48
View File
@@ -0,0 +1,48 @@
package executor
import (
"encoding/json"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"google.golang.org/protobuf/proto"
)
type Formatter func(msg proto.Message, pid int) (string, int, int)
type handler func(interface{}, []byte, *bridge.OperatorImplantBridge, func(string, int, int), string) (string, int, int)
// TODO: populate
var handlers = map[string]handler{
"ls": LsHandler,
"download": nil,
"upload": nil,
"execute-assembly": executeAssemblyHandler,
"execute-shellcode": nil,
"sideload": nil,
"spawndll": nil,
"run-extension": runExtension,
}
func GetHandlers() map[string]handler {
return handlers
}
func GetHandler(name string) handler {
h, ok := handlers[name]
if ok {
return h
}
return nil
}
func JSONFormatter(msg proto.Message, pid int) (string, int, int) {
out, err := json.Marshal(msg)
if err != nil {
return "", config.ErrorExitStatus, config.ErrorExitStatus
}
return string(out), config.SuccessExitStatus, pid
}
func sendError(err error) (string, int, int) {
return err.Error(), config.ErrorExitStatus, config.ErrorExitStatus
}
+228
View File
@@ -0,0 +1,228 @@
package executor
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"strings"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/extensions"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/implant"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
type extensionMessage struct {
Name string `json:"Name"`
Arguments []interface{} `json:"Arguments"`
}
type bofArg struct {
ArgType string `json:"type"`
Value interface{} `json:"value"`
}
func runExtension(message interface{}, _ []byte, impBridge *bridge.OperatorImplantBridge, cb func(string, int, int), outputFormat string) (string, int, int) {
var (
msg extensionMessage
extName string
export string
extArgs []byte
)
extArgsInt, ok := message.(map[string](interface{}))["Arguments"].([]interface{})
if !ok {
return sendError(errors.New("missing extension arguments"))
}
extensionName, ok := message.(map[string](interface{}))["Name"].(string)
if !ok {
return sendError(errors.New("missing extension name"))
}
msg = extensionMessage{
Name: extensionName,
Arguments: extArgsInt,
}
ext, err := extensions.GetLoadedExtension(msg.Name)
if err != nil {
return sendError(err)
}
// Load extension into implant
loadExtRequest := implant.MakeRequest(impBridge.Implant)
if loadExtRequest == nil {
return sendError(errors.New("could not create RPC request"))
}
err = extensions.LoadExtension(impBridge.Implant.GetOS(), impBridge.Implant.GetArch(), true, ext, loadExtRequest, impBridge.RPC)
if err != nil {
return sendError(err)
}
// Determine whether the extensions has dependencies (BOF),
// if so, get dependency name and extension file
if ext.DependsOn != "" {
depExt, err := extensions.GetLoadedExtension(ext.DependsOn)
if err != nil {
return sendError(err)
}
extName = depExt.CommandName
export = depExt.Entrypoint
} else {
extName = ext.CommandName
export = ext.Entrypoint
}
// Build the arguments param (depending if BOF or not)
extFilePath, err := ext.GetFileForTarget(ext.CommandName, impBridge.Implant.GetOS(), impBridge.Implant.GetArch())
if err != nil {
return sendError(err)
}
if strings.HasSuffix(extFilePath, ".o") {
// We have a BOF
extData, err := os.ReadFile(extFilePath)
if err != nil {
return sendError(err)
}
extArgs, err = parseBOFArgs(extData, ext, msg.Arguments)
if err != nil {
return sendError(err)
}
} else {
// We have a regular extension
var extArgStr []string
for _, arg := range msg.Arguments {
converted, ok := arg.(string)
if !ok {
return sendError(errors.New("arguments must be strings"))
}
extArgStr = append(extArgStr, converted)
}
extArgsLst := []byte(strings.Join(extArgStr, " "))
extArgs = []byte(extArgsLst)
}
// Call extension
callResp, err := impBridge.RPC.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
Name: extName,
ServerStore: true,
Export: export,
Request: implant.MakeRequest(impBridge.Implant),
Args: extArgs,
})
if err != nil {
return sendError(err)
}
if callResp.Response != nil && callResp.Response.Async {
impBridge.BeaconCallback(callResp.Response.TaskID, func(task *clientpb.BeaconTask) {
err := proto.Unmarshal(task.Response, callResp)
if err != nil {
cb(sendError(err))
return
}
cb(handleExtensionOutput(callResp, int(impBridge.Implant.GetPID()), outputFormat))
})
return "", config.SuccessExitStatus, int(impBridge.Implant.GetPID())
}
if callResp.Response != nil && callResp.Response.Err != "" {
return sendError(errors.New(callResp.Response.Err))
}
return handleExtensionOutput(callResp, int(impBridge.Implant.GetPID()), outputFormat)
}
func parseBOFArgs(extData []byte, extManifest *extensions.ExtensionManifest, args []interface{}) ([]byte, error) {
var (
err error
bArgs []bofArg
)
for _, arg := range args {
kv, ok := arg.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid argument: %v", arg)
}
bArgs = append(bArgs, bofArg{
ArgType: kv["type"].(string),
Value: kv["value"],
})
}
bofArgs := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
for _, a := range bArgs {
switch a.ArgType {
case "integer":
fallthrough
case "int":
if v, ok := a.Value.(float64); ok {
err = bofArgs.AddInt(uint32(v))
}
case "string":
if v, ok := a.Value.(string); ok {
err = bofArgs.AddString(v)
}
case "wstring":
if v, ok := a.Value.(string); ok {
err = bofArgs.AddWString(v)
}
case "short":
if v, ok := a.Value.(float64); ok {
err = bofArgs.AddShort(uint16(v))
}
}
if err != nil {
return nil, nil
}
}
extArgs := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
parsedArgs, err := bofArgs.GetBuffer()
if err != nil {
return nil, err
}
err = extArgs.AddString(extManifest.Entrypoint)
if err != nil {
return nil, err
}
err = extArgs.AddData(extData)
if err != nil {
return nil, err
}
err = extArgs.AddData(parsedArgs)
if err != nil {
return nil, err
}
return extArgs.GetBuffer()
}
func handleExtensionOutput(callExt *sliverpb.CallExtension, pid int, format string) (string, int, int) {
if format == "json" {
return JSONFormatter(callExt, pid)
}
return string(callExt.Output), config.SuccessExitStatus, pid
}
+107
View File
@@ -0,0 +1,107 @@
package executor
import (
"bytes"
"context"
"errors"
"fmt"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/implant"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"google.golang.org/protobuf/proto"
)
func LsHandler(arguments interface{}, _ []byte, impBridge *bridge.OperatorImplantBridge, cb func(string, int, int), outputFormat string) (string, int, int) {
path, ok := arguments.(map[string]interface{})["Path"].(string)
if !ok {
return sendError(errors.New("invalid path"))
}
lsResp, err := impBridge.RPC.Ls(context.Background(), &sliverpb.LsReq{
Path: path,
Request: implant.MakeRequest(impBridge.Implant),
})
if err != nil {
return sendError(err)
}
// Async callback
if lsResp.Response != nil && lsResp.Response.Async {
impBridge.BeaconCallback(lsResp.Response.TaskID, func(task *clientpb.BeaconTask) {
err := proto.Unmarshal(task.Response, lsResp)
if err != nil {
cb(sendError(err))
return
}
cb(handleLsOutput(lsResp, int(impBridge.Implant.GetPID()), outputFormat))
})
return "", config.SuccessExitStatus, int(impBridge.Implant.GetPID())
}
// Sync response
if lsResp.Response != nil && lsResp.Response.Err != "" {
return sendError(errors.New(lsResp.Response.Err))
}
return handleLsOutput(lsResp, int(impBridge.Implant.GetPID()), outputFormat)
}
func handleLsOutput(lsResp *sliverpb.Ls, pid int, format string) (string, int, int) {
if format == "json" {
return JSONFormatter(lsResp, pid)
}
return string(PrintLs(lsResp)), config.SuccessExitStatus, pid
}
func PrintLs(ls *sliverpb.Ls) []byte {
out := strings.Builder{}
// Generate metadata to print with the path
numberOfFiles := len(ls.Files)
var totalSize int64 = 0
var pathInfo string
for _, fileInfo := range ls.Files {
totalSize += fileInfo.Size
}
if numberOfFiles == 1 {
pathInfo = fmt.Sprintf("%s (%d item, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
} else {
pathInfo = fmt.Sprintf("%s (%d items, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
}
out.WriteString(fmt.Sprintf("%s\n", pathInfo))
out.WriteString(fmt.Sprintf("%s\n", strings.Repeat("=", len(pathInfo))))
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
sort.SliceStable(ls.Files, func(i, j int) bool {
return strings.ToLower(ls.Files[i].Name) < strings.ToLower(ls.Files[j].Name)
})
sort.SliceStable(ls.Files, func(i, j int) bool {
return ls.Files[i].ModTime < ls.Files[j].ModTime
})
for _, fileInfo := range ls.Files {
modTime := time.Unix(fileInfo.ModTime, 0)
implantLocation := time.FixedZone(ls.Timezone, int(ls.TimezoneOffset))
modTime = modTime.In(implantLocation)
if fileInfo.IsDir {
fmt.Fprintf(table, "%s\t%s\t<dir>\t%s\n", fileInfo.Mode, fileInfo.Name, modTime.Format(time.RubyDate))
} else if fileInfo.Link != "" {
fmt.Fprintf(table, "%s\t%s -> %s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
} else {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
}
}
table.Flush()
out.WriteString(outputBuf.String())
return []byte(out.String())
}
+46
View File
@@ -0,0 +1,46 @@
package implant
import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
const defaultTimeout = 60
// ActiveImplant exposes common methods between
// Sliver clientpb.Session and clientpb.Beacon
// that are required by Operator implants
type ActiveImplant interface {
GetID() string
GetHostname() string
GetPID() int32
GetOS() string
GetArch() string
GetFilename() string
GetReconnectInterval() int64
}
func MakeRequest(a ActiveImplant) *commonpb.Request {
timeout := int64(defaultTimeout)
req := &commonpb.Request{
Timeout: timeout,
}
if a == nil {
return nil
}
beacon, ok := a.(*clientpb.Beacon)
if ok {
req.BeaconID = beacon.ID
req.Async = true
return req
}
session, ok := a.(*clientpb.Session)
if ok {
req.SessionID = session.ID
req.Async = false
return req
}
return nil
}
+21 -25
View File
@@ -24,6 +24,9 @@ import (
"net"
"sync"
"github.com/bishopfox/sliver/client/prelude/bridge"
"github.com/bishopfox/sliver/client/prelude/config"
"github.com/bishopfox/sliver/client/prelude/implant"
"github.com/bishopfox/sliver/client/prelude/util"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -32,7 +35,10 @@ import (
var ImplantMapper *OperatorImplantMapper
const defaultImplantSleep = 5
const (
defaultImplantSleep = 5
defaultRange = "sliver"
)
type OperatorConfig struct {
Range string
@@ -44,42 +50,29 @@ type OperatorConfig struct {
// OperatorImplantMapper maps an OperatorConfig with
// active Sliver implant sessions/beacons
type OperatorImplantMapper struct {
implantBridges []*OperatorImplantBridge
implantBridges []*bridge.OperatorImplantBridge
conf *OperatorConfig
sync.Mutex
}
// ActiveImplant exposes common methods between
// Sliver clientpb.Session and clientpb.Beacon
// that are required by Operator implants
type ActiveImplant interface {
GetID() string
GetHostname() string
GetPID() int32
GetOS() string
GetArch() string
GetFilename() string
GetReconnectInterval() int64
}
func InitImplantMapper(conf *OperatorConfig) *OperatorImplantMapper {
if ImplantMapper == nil {
ImplantMapper = &OperatorImplantMapper{
implantBridges: make([]*OperatorImplantBridge, 0),
implantBridges: make([]*bridge.OperatorImplantBridge, 0),
conf: conf,
}
}
return ImplantMapper
}
func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string, func(*clientpb.BeaconTask))) error {
func (p *OperatorImplantMapper) AddImplant(a implant.ActiveImplant, callback func(string, func(*clientpb.BeaconTask))) error {
var pwd string
conn, err := net.Dial("tcp", p.conf.OperatorURL)
if err != nil {
return err
}
pwdResp, err := p.conf.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
Request: MakeRequest(a),
Request: implant.MakeRequest(a),
})
if err != nil {
return err
@@ -94,8 +87,11 @@ func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string
if b, ok := a.(*clientpb.Beacon); ok {
sleepTime = int(b.ReconnectInterval)
}
if p.conf.Range == "" {
p.conf.Range = defaultRange
}
beacon := OperatorBeacon{
beacon := config.OperatorBeacon{
Name: a.GetID(),
Target: p.conf.OperatorURL,
Hostname: a.GetHostname(),
@@ -103,7 +99,7 @@ func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string
Platform: a.GetOS(),
Range: p.conf.Range,
Executors: util.DetermineExecutors(a.GetOS(), a.GetArch()),
Links: make([]Instruction, 0),
Links: make([]config.Instruction, 0),
Executing: "",
Pwd: pwd,
Sleep: int(sleepTime),
@@ -112,19 +108,19 @@ func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string
if p.conf.AESKey == "" {
return errors.New("missing AES key")
}
a.GetID()
encryptionKey := p.conf.AESKey
agentConfig := AgentConfig{
agentConfig := config.AgentConfig{
Name: a.GetID(),
AESKey: encryptionKey,
Range: p.conf.Range,
Contact: "tcp",
Address: p.conf.OperatorURL,
Pid: int(a.GetPID()),
Executing: make(map[string]Instruction),
Executing: make(map[string]config.Instruction),
Sleep: int(sleepTime),
}
bridge := NewImplantBridge(&conn, a, p.conf.RPC, beacon, agentConfig, callback)
bridge := bridge.NewImplantBridge(&conn, a, p.conf.RPC, beacon, agentConfig, callback, RunCommand)
p.Lock()
p.implantBridges = append(p.implantBridges, bridge)
p.Unlock()
@@ -132,7 +128,7 @@ func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string
return nil
}
func (p *OperatorImplantMapper) RemoveImplant(imp ActiveImplant) (err error) {
func (p *OperatorImplantMapper) RemoveImplant(imp implant.ActiveImplant) (err error) {
p.Lock()
for _, bridge := range p.implantBridges {
if bridge.Implant.GetID() == imp.GetID() {
+6 -6
View File
@@ -21,16 +21,16 @@ package util
func DetermineExecutors(platform string, arch string) []string {
platformExecutors := map[string]map[string][]string{
"windows": {
"file": {"pwsh.exe", "powershell.exe", "cmd.exe", "", ""},
"executor": {"pwsh", "psh", "cmd", "bof", "exec"},
"file": {"powershell.exe", "powershell.exe", "cmd.exe", "", "", ""},
"executor": {"pwsh", "psh", "cmd", "sliver", "bof", "exec"},
},
"linux": {
"file": {"python3", "sh", "bash"},
"executor": {"python", "sh", "bash"},
"file": {"python3", "sh", "bash", ""},
"executor": {"python", "sh", "bash", "sliver"},
},
"darwin": {
"file": {"python3", "zsh", "sh", "osascript", "bash"},
"executor": {"python", "zsh", "sh", "osa", "bash"},
"file": {"python3", "zsh", "sh", "osascript", "bash", ""},
"executor": {"python", "zsh", "sh", "osa", "bash", "sliver"},
},
}
var executors []string
+1 -1
View File
@@ -33,7 +33,7 @@ else
fi
# client / command / extensions
if go test -tags=client,$TAGS ./client/command/extensions ; then
if go test -tags=client,$TAGS ./client/extensions ; then
:
else
exit 1