From 418fe757d172e5bc7af5580eca237c60a8d3501a Mon Sep 17 00:00:00 2001 From: c2biz Date: Thu, 16 Jan 2025 08:57:57 -0500 Subject: [PATCH 01/12] moved extensions command from sliver to server menu --- client/command/extensions/commands.go | 2 +- client/command/server.go | 2 ++ client/command/sliver.go | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/command/extensions/commands.go b/client/command/extensions/commands.go index 5d5e5434ef..81fdfa816d 100644 --- a/client/command/extensions/commands.go +++ b/client/command/extensions/commands.go @@ -14,7 +14,7 @@ func Commands(con *console.SliverClient) []*cobra.Command { Use: consts.ExtensionsStr, Short: "Manage extensions", Long: help.GetHelpFor([]string{consts.ExtensionsStr}), - GroupID: consts.ExecutionHelpGroup, + GroupID: consts.GenericHelpGroup, Run: func(cmd *cobra.Command, _ []string) { ExtensionsCmd(cmd, con) }, diff --git a/client/command/server.go b/client/command/server.go index 342dbfb671..286a2d5699 100644 --- a/client/command/server.go +++ b/client/command/server.go @@ -31,6 +31,7 @@ import ( "github.com/bishopfox/sliver/client/command/crack" "github.com/bishopfox/sliver/client/command/creds" "github.com/bishopfox/sliver/client/command/exit" + "github.com/bishopfox/sliver/client/command/extensions" "github.com/bishopfox/sliver/client/command/generate" "github.com/bishopfox/sliver/client/command/hosts" "github.com/bishopfox/sliver/client/command/info" @@ -91,6 +92,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command licenses.Commands, settings.Commands, alias.Commands, + extensions.Commands, armory.Commands, update.Commands, operators.Commands, diff --git a/client/command/sliver.go b/client/command/sliver.go index 0600a53ed0..f9e4d40b01 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -127,9 +127,7 @@ func SliverCommands(con *client.SliverClient) console.Commands { bind(consts.AliasHelpGroup) // [ Extensions ] - bind(consts.ExtensionHelpGroup, - extensions.Commands, - ) + bind(consts.ExtensionHelpGroup) // [ Post-command declaration setup ]---------------------------------------- From 60443f4fc76431f80cbbf64f14b88964420ececf Mon Sep 17 00:00:00 2001 From: c2biz Date: Thu, 16 Jan 2025 12:27:57 -0500 Subject: [PATCH 02/12] split extensions' subcommands: 'install', 'load', 'rm' in server context and 'list' in sliver context --- client/command/extensions/commands.go | 44 +++++++++++++++++++++------ client/command/sliver.go | 1 + 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/client/command/extensions/commands.go b/client/command/extensions/commands.go index 81fdfa816d..752370e7c3 100644 --- a/client/command/extensions/commands.go +++ b/client/command/extensions/commands.go @@ -1,11 +1,14 @@ package extensions import ( + "github.com/bishopfox/sliver/client/command/flags" "github.com/bishopfox/sliver/client/command/help" + "github.com/bishopfox/sliver/client/command/use" "github.com/bishopfox/sliver/client/console" consts "github.com/bishopfox/sliver/client/constants" "github.com/rsteube/carapace" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // Commands returns the “ command and its subcommands. @@ -20,15 +23,6 @@ func Commands(con *console.SliverClient) []*cobra.Command { }, } - extensionCmd.AddCommand(&cobra.Command{ - Use: consts.ListStr, - Short: "List extensions loaded in the current session or beacon", - Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}), - Run: func(cmd *cobra.Command, args []string) { - ExtensionsListCmd(cmd, con, args) - }, - }) - extensionLoadCmd := &cobra.Command{ Use: consts.LoadStr, Short: "Temporarily load an extension from a local directory", @@ -66,3 +60,35 @@ func Commands(con *console.SliverClient) []*cobra.Command { return []*cobra.Command{extensionCmd} } + +func SliverCommands(con *console.SliverClient) []*cobra.Command { + extensionCmd := &cobra.Command{ + Use: consts.ExtensionsStr, + Short: "Manage extensions", + Long: help.GetHelpFor([]string{consts.ExtensionsStr}), + GroupID: consts.InfoHelpGroup, + /* + Run: func(cmd *cobra.Command, _ []string) { + ExtensionsCmd(cmd, con) + }, + */ + } + + listCmd := &cobra.Command{ + Use: consts.ListStr, + Short: "List extensions loaded in the current session or beacon", + Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}), + Run: func(cmd *cobra.Command, args []string) { + ExtensionsListCmd(cmd, con, args) + }, + //GroupID: consts.InfoHelpGroup, + } + flags.Bind("use", false, listCmd, func(f *pflag.FlagSet) { + f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds") + }) + extensionCmd.AddCommand(listCmd) + + carapace.Gen(listCmd).PositionalCompletion(use.BeaconAndSessionIDCompleter(con)) + + return []*cobra.Command{extensionCmd} +} diff --git a/client/command/sliver.go b/client/command/sliver.go index f9e4d40b01..9f26771107 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -87,6 +87,7 @@ func SliverCommands(con *client.SliverClient) console.Commands { screenshot.Commands, environment.Commands, registry.Commands, + extensions.SliverCommands, ) // [ Filesystem ] From 6d2b0f83191f812ccd3a0f75c7d7b65b0917d356 Mon Sep 17 00:00:00 2001 From: c2biz Date: Thu, 16 Jan 2025 18:34:46 -0500 Subject: [PATCH 03/12] fix bug for temporarily installed (extensions load) extensions --- client/command/extensions/commands.go | 10 ++-------- client/command/extensions/extensions.go | 15 +++++++++++++++ client/command/server.go | 21 +++++++++++++++++++++ client/command/sliver.go | 2 +- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/client/command/extensions/commands.go b/client/command/extensions/commands.go index 752370e7c3..07f55554da 100644 --- a/client/command/extensions/commands.go +++ b/client/command/extensions/commands.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/pflag" ) -// Commands returns the “ command and its subcommands. +// Commands returns the 'extensions' command and its subcommands. func Commands(con *console.SliverClient) []*cobra.Command { extensionCmd := &cobra.Command{ Use: consts.ExtensionsStr, @@ -67,21 +67,15 @@ func SliverCommands(con *console.SliverClient) []*cobra.Command { Short: "Manage extensions", Long: help.GetHelpFor([]string{consts.ExtensionsStr}), GroupID: consts.InfoHelpGroup, - /* - Run: func(cmd *cobra.Command, _ []string) { - ExtensionsCmd(cmd, con) - }, - */ } listCmd := &cobra.Command{ Use: consts.ListStr, - Short: "List extensions loaded in the current session or beacon", + Short: "List extensions loaded in the current session", Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}), Run: func(cmd *cobra.Command, args []string) { ExtensionsListCmd(cmd, con, args) }, - //GroupID: consts.InfoHelpGroup, } flags.Bind("use", false, listCmd, func(f *pflag.FlagSet) { f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds") diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 580be270a7..94ebe070f8 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "strings" "github.com/bishopfox/sliver/client/assets" @@ -115,6 +116,20 @@ func getInstalledManifests() map[string]*ExtensionManifest { return installedManifests } +// GetLoadedExtensionPaths returns a list of manifest paths for all loaded extensions, +// regardless of whether they were installed permanently or loaded temporarily. This +// includes the combined set of extensions from both the filesystem and in-memory state. +func GetLoadedExtensionPaths() []string { + paths := []string{} + for _, ext := range loadedExtensions { + if ext.Manifest != nil && ext.Manifest.RootPath != "" { + manifestPath := filepath.Join(ext.Manifest.RootPath, "extension.json") + paths = append(paths, manifestPath) + } + } + return paths +} + // ExtensionsCommandNameCompleter - Completer for installed extensions command names. func ExtensionsCommandNameCompleter(con *console.SliverClient) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action { diff --git a/client/command/server.go b/client/command/server.go index 286a2d5699..8998375fa7 100644 --- a/client/command/server.go +++ b/client/command/server.go @@ -19,6 +19,7 @@ package command */ import ( + "fmt" "os" "github.com/bishopfox/sliver/client/command/alias" @@ -133,6 +134,26 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command // [ Post-command declaration setup ]----------------------------------------- + // Load Extensions + // Similar to the sliver context loading, without adding the commands to the + // server command tree. This is done to ensure that the extensions are loaded + // before the server is started, so that the extensions are registered. + extensionManifests := extensions.GetLoadedExtensionPaths() + for _, manifest := range extensionManifests { + _, err := extensions.LoadExtensionManifest(manifest) + // Absorb error in case there's no extensions manifest + if err != nil { + //con doesn't appear to be initialised here? + //con.PrintErrorf("Failed to load extension: %s", err) + fmt.Printf("Failed to load extension: %s\n", err) + continue + } + + //for _, ext := range mext.ExtCommand { + // extensions.ExtensionRegisterCommand(ext, server, con) + //} + } + // Everything below this line should preferably not be any command binding // (although you can do so without fear). If there are any final modifications // to make to the server menu command tree, it time to do them here. diff --git a/client/command/sliver.go b/client/command/sliver.go index 9f26771107..09786c8737 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -143,7 +143,7 @@ func SliverCommands(con *client.SliverClient) console.Commands { } // Load Extensions - extensionManifests := assets.GetInstalledExtensionManifests() + extensionManifests := extensions.GetLoadedExtensionPaths() for _, manifest := range extensionManifests { mext, err := extensions.LoadExtensionManifest(manifest) // Absorb error in case there's no extensions manifest From ae0c106595d3483527a615d441aa0361d7400b01 Mon Sep 17 00:00:00 2001 From: c2biz Date: Thu, 16 Jan 2025 23:53:18 -0500 Subject: [PATCH 04/12] add GetAllExtensionManifests() to obtain installed and temp-installed extensions --- client/command/extensions/extensions.go | 44 +++++++++++++++++-------- client/command/extensions/load.go | 3 +- client/command/server.go | 9 +++-- client/command/sliver.go | 6 ++-- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 94ebe070f8..31bdc44905 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -36,7 +36,7 @@ import ( // ExtensionsCmd - List information about installed extensions. func ExtensionsCmd(cmd *cobra.Command, con *console.SliverClient) { - if 0 < len(getInstalledManifests()) { + if 0 < len(GetAllExtensionManifests()) { PrintExtensions(con) } else { con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n") @@ -64,7 +64,7 @@ func PrintExtensions(con *console.SliverClient) { {Number: 5, Align: text.AlignCenter}, }) - installedManifests := getInstalledManifests() + installedManifests := GetAllExtensionManifests() for _, extension := range loadedExtensions { //for _, extension := range extensionm.ExtCommand { installed := "" @@ -111,23 +111,41 @@ func getInstalledManifests() map[string]*ExtensionManifest { if err != nil { continue } + manifest.RootPath = filepath.Dir(manifestPath) installedManifests[manifest.Name] = manifest } return installedManifests } -// GetLoadedExtensionPaths returns a list of manifest paths for all loaded extensions, -// regardless of whether they were installed permanently or loaded temporarily. This -// includes the combined set of extensions from both the filesystem and in-memory state. -func GetLoadedExtensionPaths() []string { - paths := []string{} - for _, ext := range loadedExtensions { - if ext.Manifest != nil && ext.Manifest.RootPath != "" { - manifestPath := filepath.Join(ext.Manifest.RootPath, "extension.json") - paths = append(paths, manifestPath) - } +// getTemporarilyLoadedManifests returns a map of extension manifests that are currently +// loaded into memory but not permanently installed. The map is keyed by the manifest's +// Name field. +func getTemporarilyLoadedManifests() map[string]*ExtensionManifest { + tempManifests := map[string]*ExtensionManifest{} + for name, manifest := range loadedManifests { + tempManifests[name] = manifest + } + return tempManifests +} + +// GetAllExtensionManifests returns a combined map of all extension manifests, +// including both permanently installed and temporarily loaded extensions. +// If a manifest exists in both states, the temporarily loaded version takes precedence +// to allow for development and testing of modified extensions. +func GetAllExtensionManifests() map[string]*ExtensionManifest { + allManifests := make(map[string]*ExtensionManifest) + + // Add installed manifests first + for name, manifest := range getInstalledManifests() { + allManifests[name] = manifest + } + + // Add/override with temporarily loaded manifests + for name, manifest := range getTemporarilyLoadedManifests() { + allManifests[name] = manifest } - return paths + + return allManifests } // ExtensionsCommandNameCompleter - Completer for installed extensions command names. diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 0f687ba763..8573e304b8 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -187,7 +187,8 @@ func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) { func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest { ret := &ExtensionManifest{ - Name: old.CommandName, //treating old command name as the manifest name to avoid weird chars mostly + //Name: old.CommandName, //treating old command name as the manifest name to avoid weird chars mostly + Name: old.Name, // Use name field to avoid mismatch with loadedExtensions map which must use command_name as its key Version: old.Version, ExtensionAuthor: old.ExtensionAuthor, OriginalAuthor: old.OriginalAuthor, diff --git a/client/command/server.go b/client/command/server.go index 8998375fa7..678e14e6d9 100644 --- a/client/command/server.go +++ b/client/command/server.go @@ -21,6 +21,7 @@ package command import ( "fmt" "os" + "path/filepath" "github.com/bishopfox/sliver/client/command/alias" "github.com/bishopfox/sliver/client/command/armory" @@ -138,9 +139,11 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command // Similar to the sliver context loading, without adding the commands to the // server command tree. This is done to ensure that the extensions are loaded // before the server is started, so that the extensions are registered. - extensionManifests := extensions.GetLoadedExtensionPaths() + // Load Extensions + extensionManifests := extensions.GetAllExtensionManifests() // returns map[string]*ExtensionManifest for _, manifest := range extensionManifests { - _, err := extensions.LoadExtensionManifest(manifest) + manifestPath := filepath.Join(manifest.RootPath, extensions.ManifestFileName) + _, err := extensions.LoadExtensionManifest(manifestPath) // Absorb error in case there's no extensions manifest if err != nil { //con doesn't appear to be initialised here? @@ -150,7 +153,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command } //for _, ext := range mext.ExtCommand { - // extensions.ExtensionRegisterCommand(ext, server, con) + // extensions.ExtensionRegisterCommand(ext, sliver, con) //} } diff --git a/client/command/sliver.go b/client/command/sliver.go index 09786c8737..5a77ea6de5 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -20,6 +20,7 @@ package command import ( "fmt" + "path/filepath" "github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/command/alias" @@ -143,9 +144,10 @@ func SliverCommands(con *client.SliverClient) console.Commands { } // Load Extensions - extensionManifests := extensions.GetLoadedExtensionPaths() + extensionManifests := extensions.GetAllExtensionManifests() // returns map[string]*ExtensionManifest for _, manifest := range extensionManifests { - mext, err := extensions.LoadExtensionManifest(manifest) + manifestPath := filepath.Join(manifest.RootPath, extensions.ManifestFileName) + mext, err := extensions.LoadExtensionManifest(manifestPath) // Absorb error in case there's no extensions manifest if err != nil { //con doesn't appear to be initialised here? From 0b185cc885f3a5d166160b555bf66bee1c81d57a Mon Sep 17 00:00:00 2001 From: c2biz Date: Fri, 17 Jan 2025 00:20:28 -0500 Subject: [PATCH 05/12] fix bin path bug for temp-installed extensions and display bug in PrintExtensions() --- client/command/extensions/extensions.go | 2 +- client/command/extensions/load.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 31bdc44905..46b399bcda 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -64,7 +64,7 @@ func PrintExtensions(con *console.SliverClient) { {Number: 5, Align: text.AlignCenter}, }) - installedManifests := GetAllExtensionManifests() + installedManifests := getInstalledManifests() for _, extension := range loadedExtensions { //for _, extension := range extensionm.ExtCommand { installed := "" diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 8573e304b8..3b8b8cf9b3 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -125,7 +125,13 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin filePath := "" for _, extFile := range e.Files { if targetOS == extFile.OS && targetArch == extFile.Arch { - filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path) + if e.Manifest.RootPath != "" { + // Use RootPath for temporarily loaded extensions + filePath = path.Join(e.Manifest.RootPath, extFile.Path) + } else { + // Fall back to extensions dir for installed extensions + filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path) + } break } } From 12e44644ed24ba5170dad12aa1645a02eb9f31f8 Mon Sep 17 00:00:00 2001 From: c2biz Date: Fri, 17 Jan 2025 15:16:03 -0500 Subject: [PATCH 06/12] introduce package_name field --- client/command/extensions/extensions.go | 3 +++ client/command/extensions/install.go | 26 +++++++++++++++++++++++-- client/command/extensions/load.go | 17 ++++++++++++---- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 46b399bcda..81aa6ce507 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -98,6 +98,9 @@ func extensionPlatforms(extension *ExtCommand) []string { return keys } +// getInstalledManifests - Returns a mapping of extension names to their parsed manifest objects. +// Reads all installed extension manifests from disk, ignoring any that cannot be read or parsed. +// The returned manifests have their RootPath set to the directory containing their manifest file. func getInstalledManifests() map[string]*ExtensionManifest { manifestPaths := assets.GetInstalledExtensionManifests() installedManifests := map[string]*ExtensionManifest{} diff --git a/client/command/extensions/install.go b/client/command/extensions/install.go index 715c68da07..eae789501f 100644 --- a/client/command/extensions/install.go +++ b/client/command/extensions/install.go @@ -43,7 +43,22 @@ func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverClient, args [] InstallFromDir(extLocalPath, true, con, strings.HasSuffix(extLocalPath, ".tar.gz")) } -// Install an extension from a directory +// InstallFromDir installs a Sliver extension from either a local directory or gzipped archive. +// It reads the extension manifest, validates it, and copies all required files to the extensions +// directory. If an extension with the same name already exists, it can optionally prompt for +// overwrite confirmation. +// +// Parameters: +// - extLocalPath: Path to the source directory or gzipped archive containing the extension +// - promptToOverwrite: If true, prompts for confirmation before overwriting existing extension +// - con: Sliver console client for displaying status and error messages +// - isGz: Whether the source is a gzipped archive (true) or directory (false) +// +// The function will return early with error messages printed to console if: +// - The manifest cannot be read or parsed +// - Required directories cannot be created +// - File copy operations fail +// - User declines overwrite when prompted func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.SliverClient, isGz bool) { var manifestData []byte var err error @@ -64,8 +79,15 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl return } + // Use package name if available, otherwise use extension name + // (Note, for v1 manifests this will actually be command_name) + packageID := manifestF.Name + if manifestF.PackageName != "" { + packageID = manifestF.PackageName + } + //create repo path - minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name)) + minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(packageID)) if _, err := os.Stat(minstallPath); !os.IsNotExist(err) { if promptToOverwrite { con.PrintInfof("Extension '%s' already exists", manifestF.Name) diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 3b8b8cf9b3..3a2fe32324 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -80,6 +80,7 @@ type ExtensionManifest_ struct { type ExtensionManifest struct { Name string `json:"name"` + PackageName string `json:"package_name"` Version string `json:"version"` ExtensionAuthor string `json:"extension_author"` OriginalAuthor string `json:"original_author"` @@ -146,10 +147,19 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin return filePath, nil } -// ExtensionLoadCmd - Load extension command. +// ExtensionLoadCmd - Temporarily installs an extension from a local directory into the client. +// The extension must contain a valid manifest file. If commands from the extension +// already exist, the user will be prompted to overwrite them. func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { dirPath := args[0] - // dirPath := ctx.Args.String("dir-path") + + // Add directory check + fileInfo, err := os.Stat(dirPath) + if err != nil || !fileInfo.IsDir() { + con.PrintErrorf("Path is not a directory: %s\n", dirPath) + return + } + manifest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName)) if err != nil { return @@ -193,8 +203,7 @@ func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) { func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest { ret := &ExtensionManifest{ - //Name: old.CommandName, //treating old command name as the manifest name to avoid weird chars mostly - Name: old.Name, // Use name field to avoid mismatch with loadedExtensions map which must use command_name as its key + Name: old.CommandName, //treating old command name as the manifest name to avoid weird chars mostly Version: old.Version, ExtensionAuthor: old.ExtensionAuthor, OriginalAuthor: old.OriginalAuthor, From 61ed176683cb8f3aa2479238d87d8977ef6d7ef2 Mon Sep 17 00:00:00 2001 From: c2biz Date: Fri, 17 Jan 2025 17:51:08 -0500 Subject: [PATCH 07/12] fix dupe issue in GetAllExtensionManifests() --- client/command/extensions/extensions.go | 33 ++++++++++++++----------- client/command/extensions/load.go | 14 +++++++++-- client/command/server.go | 11 +++------ client/command/sliver.go | 6 ++--- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 81aa6ce507..1af66eec16 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -36,7 +36,7 @@ import ( // ExtensionsCmd - List information about installed extensions. func ExtensionsCmd(cmd *cobra.Command, con *console.SliverClient) { - if 0 < len(GetAllExtensionManifests()) { + if len(GetAllExtensionManifests()) > 0 { PrintExtensions(con) } else { con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n") @@ -131,24 +131,29 @@ func getTemporarilyLoadedManifests() map[string]*ExtensionManifest { return tempManifests } -// GetAllExtensionManifests returns a combined map of all extension manifests, -// including both permanently installed and temporarily loaded extensions. -// If a manifest exists in both states, the temporarily loaded version takes precedence -// to allow for development and testing of modified extensions. -func GetAllExtensionManifests() map[string]*ExtensionManifest { - allManifests := make(map[string]*ExtensionManifest) +// GetAllExtensionManifests returns a combined list of manifest file paths from +// both installed and temporarily loaded extensions +func GetAllExtensionManifests() []string { + manifestPaths := make(map[string]struct{}) // use map for deduplication - // Add installed manifests first - for name, manifest := range getInstalledManifests() { - allManifests[name] = manifest + // Add installed manifests + for _, manifest := range getInstalledManifests() { + manifestPath := filepath.Join(manifest.RootPath, ManifestFileName) + manifestPaths[manifestPath] = struct{}{} } - // Add/override with temporarily loaded manifests - for name, manifest := range getTemporarilyLoadedManifests() { - allManifests[name] = manifest + // Add temporarily loaded manifests + for _, manifest := range getTemporarilyLoadedManifests() { + manifestPath := filepath.Join(manifest.RootPath, ManifestFileName) + manifestPaths[manifestPath] = struct{}{} } - return allManifests + // Convert to slice + paths := make([]string, 0, len(manifestPaths)) + for path := range manifestPaths { + paths = append(paths, path) + } + return paths } // ExtensionsCommandNameCompleter - Completer for installed extensions command names. diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 3a2fe32324..32c6865628 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -181,7 +181,11 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri } } -// LoadExtensionManifest - Parse extension files. +// LoadExtensionManifest loads and parses an extension manifest file from the given path. +// It registers each command defined in the manifest into the loadedExtensions map +// and registers the complete manifest into loadedManifests. A single manifest may +// contain multiple extension commands. The manifest's RootPath is set to its containing +// directory. Returns the parsed manifest and any errors encountered. func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) { data, err := os.ReadFile(manifestPath) if err != nil { @@ -295,7 +299,13 @@ func validManifest(manifest *ExtensionManifest) error { return nil } -// ExtensionRegisterCommand - Register a new extension command +// ExtensionRegisterCommand adds an extension command to the cobra command system. +// It validates the extension's arguments, updates the loadedExtensions map, and +// creates a cobra.Command with proper usage text, help documentation, and argument +// handling. The command is added as a subcommand to the provided parent cobra.Command. +// Arguments are displayed in the help text as uppercase, with optional args in square +// brackets. The help text includes sections for command usage, description, and detailed +// argument specifications. func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *console.SliverClient) { if errInvalidArgs := checkExtensionArgs(extCmd); errInvalidArgs != nil { con.PrintErrorf(errInvalidArgs.Error()) diff --git a/client/command/server.go b/client/command/server.go index 678e14e6d9..b6e815beeb 100644 --- a/client/command/server.go +++ b/client/command/server.go @@ -21,7 +21,6 @@ package command import ( "fmt" "os" - "path/filepath" "github.com/bishopfox/sliver/client/command/alias" "github.com/bishopfox/sliver/client/command/armory" @@ -136,14 +135,12 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command // [ Post-command declaration setup ]----------------------------------------- // Load Extensions - // Similar to the sliver context loading, without adding the commands to the - // server command tree. This is done to ensure that the extensions are loaded + // Similar to the SliverCommand loading, without adding the commands to the + // Server command tree. This is done to ensure that the extensions are loaded // before the server is started, so that the extensions are registered. - // Load Extensions - extensionManifests := extensions.GetAllExtensionManifests() // returns map[string]*ExtensionManifest + extensionManifests := extensions.GetAllExtensionManifests() for _, manifest := range extensionManifests { - manifestPath := filepath.Join(manifest.RootPath, extensions.ManifestFileName) - _, err := extensions.LoadExtensionManifest(manifestPath) + _, err := extensions.LoadExtensionManifest(manifest) // Absorb error in case there's no extensions manifest if err != nil { //con doesn't appear to be initialised here? diff --git a/client/command/sliver.go b/client/command/sliver.go index 5a77ea6de5..0cc1182d67 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -20,7 +20,6 @@ package command import ( "fmt" - "path/filepath" "github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/command/alias" @@ -144,10 +143,9 @@ func SliverCommands(con *client.SliverClient) console.Commands { } // Load Extensions - extensionManifests := extensions.GetAllExtensionManifests() // returns map[string]*ExtensionManifest + extensionManifests := extensions.GetAllExtensionManifests() for _, manifest := range extensionManifests { - manifestPath := filepath.Join(manifest.RootPath, extensions.ManifestFileName) - mext, err := extensions.LoadExtensionManifest(manifestPath) + mext, err := extensions.LoadExtensionManifest(manifest) // Absorb error in case there's no extensions manifest if err != nil { //con doesn't appear to be initialised here? From b84037af111eebeaedb3bc8cf92b86d1434e7571 Mon Sep 17 00:00:00 2001 From: c2biz Date: Fri, 17 Jan 2025 18:11:11 -0500 Subject: [PATCH 08/12] fix bug in PrintExtensions --- client/command/extensions/extensions.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/command/extensions/extensions.go b/client/command/extensions/extensions.go index 1af66eec16..3a92efb493 100644 --- a/client/command/extensions/extensions.go +++ b/client/command/extensions/extensions.go @@ -68,8 +68,12 @@ func PrintExtensions(con *console.SliverClient) { for _, extension := range loadedExtensions { //for _, extension := range extensionm.ExtCommand { installed := "" - if _, ok := installedManifests[extension.Manifest.Name]; ok { - installed = "✅" + //if _, ok := installedManifests[extension.Manifest.Name]; ok { + for _, installedManifest := range installedManifests { + if extension.Manifest.RootPath == installedManifest.RootPath { + installed = "✅" + break + } } tw.AppendRow(table.Row{ extension.Manifest.Name, From debc03203c6c39c4e08c4c671c78aa9d06252a43 Mon Sep 17 00:00:00 2001 From: c2biz Date: Mon, 20 Jan 2025 14:07:45 -0500 Subject: [PATCH 09/12] restrict 'extensions list' sub-command to session mode --- client/command/extensions/commands.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/command/extensions/commands.go b/client/command/extensions/commands.go index 07f55554da..4cea7adf66 100644 --- a/client/command/extensions/commands.go +++ b/client/command/extensions/commands.go @@ -63,10 +63,11 @@ func Commands(con *console.SliverClient) []*cobra.Command { func SliverCommands(con *console.SliverClient) []*cobra.Command { extensionCmd := &cobra.Command{ - Use: consts.ExtensionsStr, - Short: "Manage extensions", - Long: help.GetHelpFor([]string{consts.ExtensionsStr}), - GroupID: consts.InfoHelpGroup, + Use: consts.ExtensionsStr, + Short: "Manage extensions", + Long: help.GetHelpFor([]string{consts.ExtensionsStr}), + GroupID: consts.InfoHelpGroup, + Annotations: flags.RestrictTargets(consts.SessionCmdsFilter), // restrict to session targets since we cannot `list` "loaded" extensions from beacon mode } listCmd := &cobra.Command{ From ffcfa8d1b58afaa5186457a4eafd30790ca0fcfd Mon Sep 17 00:00:00 2001 From: c2biz Date: Mon, 20 Jan 2025 14:20:09 -0500 Subject: [PATCH 10/12] add extension short help to SliverCommand help menu --- client/command/extensions/load.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 32c6865628..1064bd3d11 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -313,7 +313,6 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso } loadedExtensions[extCmd.CommandName] = extCmd - //helpMsg := extCmd.Help usage := strings.Builder{} usage.WriteString(extCmd.CommandName) @@ -365,9 +364,9 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso // Command extensionCmd := &cobra.Command{ - Use: usage.String(), //extCmd.CommandName, - //Short: helpMsg.String(), doesn't appear to be used? - Long: help.FormatHelpTmpl(longHelp.String()), + Use: usage.String(), + Short: extCmd.Help, + Long: help.FormatHelpTmpl(longHelp.String()), Run: func(cmd *cobra.Command, args []string) { runExtensionCmd(cmd, con, args) }, From 5c2848c84cd9036433c99a0128594ef41eac0ac1 Mon Sep 17 00:00:00 2001 From: c2biz Date: Tue, 21 Jan 2025 07:26:06 -0500 Subject: [PATCH 11/12] improve extensions list output --- client/command/extensions/list.go | 116 ++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/client/command/extensions/list.go b/client/command/extensions/list.go index 52de4a9760..7edc9377a4 100644 --- a/client/command/extensions/list.go +++ b/client/command/extensions/list.go @@ -20,12 +20,116 @@ package extensions import ( "context" + "crypto/sha256" + "encoding/hex" + "os" + "path/filepath" + "github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/protobuf/sliverpb" + "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" ) +// ExtensionMatch holds the details of a matched extension + +type ExtensionMatch struct { + CommandName string + Hash string + BinPath string +} + +// FindExtensionMatches searches through loaded extensions for matching hashes +// Returns a map of hash to ExtensionMatch (match will be nil if hash wasn't found) +func FindExtensionMatches(targetHashes []string) map[string]*ExtensionMatch { + results := make(map[string]*ExtensionMatch) + pathCache := make(map[string]*ExtensionMatch) + + // Initialize results map with all target hashes + for _, hash := range targetHashes { + results[hash] = nil + } + + // Search for matches + for targetHash := range results { + for _, extCmd := range loadedExtensions { + if extCmd == nil || len(extCmd.Files) == 0 { + continue + } + + for _, file := range extCmd.Files { + fullPath := filepath.Join(extCmd.Manifest.RootPath, file.Path) + + // Check cache first + var match *ExtensionMatch + if cached, exists := pathCache[fullPath]; exists { + match = cached + } else { + // Calculate hash if not cached + fileData, err := os.ReadFile(fullPath) + if err != nil { + continue + } + + hashBytes := sha256.Sum256(fileData) + fileHash := hex.EncodeToString(hashBytes[:]) + + match = &ExtensionMatch{ + CommandName: extCmd.CommandName, + Hash: fileHash, + BinPath: fullPath, + } + pathCache[fullPath] = match + } + + if match.Hash == targetHash { + results[targetHash] = match + break + } + } + + if results[targetHash] != nil { + break + } + } + } + + return results +} + +// PrintExtensionMatches prints the extension matches in a formatted table +func PrintExtensionMatches(matches map[string]*ExtensionMatch, con *console.SliverClient) { + tw := table.NewWriter() + tw.SetStyle(settings.GetTableStyle(con)) + tw.AppendHeader(table.Row{ + "Command Name", + "Sha256 Hash", + "Bin Path", + }) + tw.SortBy([]table.SortBy{ + {Name: "Command Name", Mode: table.Asc}, + }) + + for hash, match := range matches { + if match != nil { + tw.AppendRow(table.Row{ + match.CommandName, + hash, + match.BinPath, + }) + } else { + tw.AppendRow(table.Row{ + "", + hash, + "", + }) + } + } + + con.Println(tw.Render()) +} + // ExtensionsListCmd - List all extension loaded on the active session/beacon. func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { session := con.ActiveTarget.GetSessionInteractive() @@ -45,10 +149,12 @@ func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []str con.PrintErrorf("%s\n", extList.Response.Err) return } - if len(extList.Names) > 0 { - con.PrintInfof("Loaded extensions:\n") - for _, ext := range extList.Names { - con.Printf("- %s\n", ext) - } + + if len(extList.Names) == 0 { + return } + + con.PrintInfof("Loaded extensions:\n\n") + matches := FindExtensionMatches(extList.Names) + PrintExtensionMatches(matches, con) } From 99571dc73ecdc2bcef2bbacdf5a0404600d76f6e Mon Sep 17 00:00:00 2001 From: c2biz Date: Tue, 21 Jan 2025 10:54:32 -0500 Subject: [PATCH 12/12] fix bug in extensions rm command --- client/command/extensions/remove.go | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/client/command/extensions/remove.go b/client/command/extensions/remove.go index 9cd81f1341..708e613355 100644 --- a/client/command/extensions/remove.go +++ b/client/command/extensions/remove.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/AlecAivazis/survey/v2" "github.com/bishopfox/sliver/client/assets" @@ -86,12 +87,35 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien return false, errors.New("command name is required") } if man, ok := loadedManifests[manifestName]; ok { - //foudn the manifest - //delet it - extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName)) + // Found the manifest + var extPath string + if man.RootPath != "" { + // Use RootPath for temporarily loaded extensions + extPath = man.RootPath + } else { + // Fall back to extensions dir for installed extensions + extPath = filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName)) + } + if _, err := os.Stat(extPath); os.IsNotExist(err) { return true, nil } + + // If path is outside extensions directory, prompt for confirmation + if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) { + confirm := false + prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)} + survey.AskOne(prompt, &confirm) + if !confirm { + // Skip the forceRemoveAll but continue with the rest + delete(loadedManifests, manifestName) + for _, cmd := range man.ExtCommand { + delete(loadedExtensions, cmd.CommandName) + } + return true, nil + } + } + forceRemoveAll(extPath) delete(loadedManifests, manifestName) for _, cmd := range man.ExtCommand {