mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
105
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
804e149113 | ||
|
|
9ced4cfd50 | ||
|
|
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 | ||
|
|
87b1efaa5f | ||
|
|
009e505a54 | ||
|
|
c0dd2d5b51 | ||
|
|
6dab9dc6c5 | ||
|
|
854249fa25 | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
8ea7eb4236 | ||
|
|
8f3c3ec1e0 | ||
|
|
8d4bf9c759 | ||
|
|
628757250e | ||
|
|
85094fc695 | ||
|
|
0a88ad77f7 | ||
|
|
4d04f08421 | ||
|
|
4a01971f7d | ||
|
|
977c63dc16 | ||
|
|
2d65d7d6a5 | ||
|
|
4f5ca51cfe | ||
|
|
83208eeab4 | ||
|
|
21674a9f0a | ||
|
|
a18bda14e2 | ||
|
|
37f9670783 | ||
|
|
1745886622 | ||
|
|
bd7361825c | ||
|
|
32af81c132 | ||
|
|
818127349c | ||
|
|
a712bead88 | ||
|
|
aa86a1d12c | ||
|
|
afb58f7155 | ||
|
|
97d5adccd7 | ||
|
|
83cc755388 | ||
|
|
4dc1fe145c | ||
|
|
5e27b23bde | ||
|
|
6011d23a9f | ||
|
|
976d720043 | ||
|
|
86f41e9abc | ||
|
|
8b9fc399fa | ||
|
|
27c75c14e6 | ||
|
|
25d242230b | ||
|
|
b4345226c1 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b |
No files matched your search
@@ -42,3 +42,4 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -31,6 +32,18 @@ const (
|
||||
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
|
||||
type ClientSettings struct {
|
||||
TableStyle string `json:"tables" yaml:"tables"`
|
||||
@@ -41,6 +54,8 @@ type ClientSettings struct {
|
||||
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
||||
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
|
||||
@@ -67,6 +82,8 @@ func LoadSettings() (*ClientSettings, error) {
|
||||
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 {
|
||||
return settings, err
|
||||
}
|
||||
@@ -78,6 +95,38 @@ func LoadSettings() (*ClientSettings, error) {
|
||||
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 {
|
||||
return &ClientSettings{
|
||||
TableStyle: "SliverDefault",
|
||||
@@ -87,9 +136,13 @@ func defaultSettings() *ClientSettings {
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
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
|
||||
func SaveSettings(settings *ClientSettings) error {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
|
||||
@@ -21,6 +21,8 @@ always_overflow: true
|
||||
vim_mode: true
|
||||
user_connect: true
|
||||
console_logs: false
|
||||
prompt: "basic"
|
||||
prompt_template: "{{.Host}}"
|
||||
`)
|
||||
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write yaml settings: %v", err)
|
||||
@@ -54,6 +56,12 @@ console_logs: false
|
||||
if settings.ConsoleLogs {
|
||||
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) {
|
||||
@@ -71,6 +79,8 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||
VimMode: true,
|
||||
UserConnect: true,
|
||||
ConsoleLogs: false,
|
||||
PromptStyle: PromptStyleOperatorHost,
|
||||
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
@@ -87,6 +97,12 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||
if settings.TableStyle != "Legacy" {
|
||||
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 {
|
||||
t.Fatalf("expected yaml settings to exist: %v", err)
|
||||
}
|
||||
@@ -108,6 +124,12 @@ func TestLoadSettingsWritesDefault(t *testing.T) {
|
||||
if settings.TableStyle != "SliverDefault" {
|
||||
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())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", err)
|
||||
|
||||
@@ -26,16 +26,16 @@ import (
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
func selectConfig() (string, *assets.ClientConfig) {
|
||||
configs := assets.GetConfigs()
|
||||
|
||||
if len(configs) == 0 {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(configs) == 1 {
|
||||
for _, config := range configs {
|
||||
return config
|
||||
for key, config := range configs {
|
||||
return key, config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
|
||||
err := forms.Select("Select a server:", keys, &selection)
|
||||
if err != nil {
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
@@ -30,7 +28,6 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
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())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
configKey, config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -72,40 +67,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
}
|
||||
|
||||
var rpc rpcpb.SliverRPCClient
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||
}
|
||||
|
||||
// Close the RPC connection once exiting
|
||||
post = func(_ *cobra.Command, _ []string) error {
|
||||
if ln != nil {
|
||||
return ln.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
return con.CloseConnection()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
tw := table.NewWriter()
|
||||
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
|
||||
|
||||
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
style.Render(pkg.URL),
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Bundles"))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Name",
|
||||
|
||||
@@ -635,20 +635,15 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), entry.Alias.CommandName, promptToOverwrite, con)
|
||||
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
@@ -729,23 +724,45 @@ func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clien
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpFile.Sync()
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
|
||||
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeArmoryTempFile(data []byte) (string, error) {
|
||||
if len(data) == 0 {
|
||||
return "", errors.New("downloaded archive is empty")
|
||||
}
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
for len(data) > 0 {
|
||||
n, err := tmpFile.Write(data)
|
||||
if err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
data = data[n:]
|
||||
}
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
return tmpFileName, nil
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
||||
if len(extensionUpdates) != 1 {
|
||||
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{
|
||||
"Package Name",
|
||||
"Package Type",
|
||||
|
||||
@@ -178,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
}
|
||||
|
||||
for _, beacon := range beacons {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
activeBeacon := con.ActiveTarget.GetBeacon()
|
||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if beacon.Integrity == "" {
|
||||
beacon.Integrity = "-"
|
||||
@@ -192,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.RemoteAddress),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
|
||||
}
|
||||
|
||||
if windowsBeaconInList {
|
||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
|
||||
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
|
||||
}
|
||||
|
||||
rowEntries = append(rowEntries, []string{
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
style.Render(beacon.Locale),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}...)
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -58,28 +57,28 @@ func printCertificateAuthorityInfo(con *console.SliverClient, authData []*client
|
||||
}
|
||||
|
||||
for _, authority := range authData {
|
||||
rowColor := console.Normal
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
rowStyle = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.CreationTime),
|
||||
rowStyle.Render(authority.Type),
|
||||
rowStyle.Render(authority.KeyAlgorithm),
|
||||
rowStyle.Render(authority.ValidityStart),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
func checkCertExpiry(expiryTime time.Time) string {
|
||||
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
|
||||
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
|
||||
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
|
||||
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) {
|
||||
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
|
||||
}
|
||||
|
||||
for _, cert := range certData {
|
||||
rowColor := console.Normal
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
||||
// This should not error out, but if it does, the row will not be colored
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
rowStyle = checkCertExpiry(expiry)
|
||||
}
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.CreationTime),
|
||||
rowStyle.Render(cert.Type),
|
||||
rowStyle.Render(cert.KeyAlgorithm),
|
||||
rowStyle.Render(cert.ValidityStart),
|
||||
rowStyle.Render(cert.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
|
||||
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
wordlistTable := table.NewWriter()
|
||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
||||
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
|
||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
rulesTable := table.NewWriter()
|
||||
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
|
||||
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
|
||||
rulesTable.SetStyle(settings.GetTableStyle(con))
|
||||
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
hcTable := table.NewWriter()
|
||||
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
|
||||
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
|
||||
hcTable.SetStyle(settings.GetTableStyle(con))
|
||||
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,53 +129,63 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
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.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, 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("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
|
||||
if 0 < len(cracker.CUDA) {
|
||||
for _, cuda := range cracker.CUDA {
|
||||
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.Bold + "Memory" + console.Normal, 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.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
|
||||
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.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.Metal) {
|
||||
for _, metal := range cracker.Metal {
|
||||
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.Bold + "Memory" + console.Normal, 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.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
|
||||
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.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.OpenCL) {
|
||||
for _, openCL := range cracker.OpenCL {
|
||||
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.Bold + "Memory" + console.Normal, 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.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
|
||||
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.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
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.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if collection != "" {
|
||||
tw.SetTitle(console.Bold + collection + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render(collection))
|
||||
} else {
|
||||
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Default Collection"))
|
||||
}
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
|
||||
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
if chromeExt != nil {
|
||||
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 ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// 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 {
|
||||
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 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")
|
||||
confirm := false
|
||||
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")
|
||||
shell := readline.NewShell()
|
||||
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",
|
||||
|
||||
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 {
|
||||
line, err := shell.Readline()
|
||||
|
||||
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
}
|
||||
if chromeExt != nil {
|
||||
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 ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// 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 {
|
||||
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 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")
|
||||
confirm := false
|
||||
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 {
|
||||
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("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = forms.Confirm("Kill and restart the process?", &confirm)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -26,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
@@ -44,6 +46,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
@@ -171,11 +187,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
}
|
||||
flags.Bind("", false, migrateCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("disable-sgn", "S", true, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.Uint32P("pid", "p", 0, "process id to migrate into")
|
||||
f.StringP("process-name", "n", "", "name of the process to migrate into")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(migrateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(migrateCmd).PositionalCompletion(carapace.ActionValues().Usage("PID of process to migrate into"))
|
||||
|
||||
msfCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -157,10 +157,25 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
|
||||
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
|
||||
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: hostProc,
|
||||
EnablePTY: !noPty,
|
||||
Rows: rows,
|
||||
Cols: cols,
|
||||
TunnelID: tunnel.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -200,6 +215,12 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
}
|
||||
}
|
||||
|
||||
stopPtyResize := func() {}
|
||||
if !noPty {
|
||||
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
|
||||
}
|
||||
defer stopPtyResize()
|
||||
|
||||
log.Printf("Starting stdin/stdout shell ...")
|
||||
go func() {
|
||||
n, err := io.Copy(os.Stdout, tunnel)
|
||||
|
||||
@@ -44,6 +44,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
@@ -84,6 +90,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,14 +20,19 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// MigrateCmd - Windows only, inject an implant into another process
|
||||
@@ -55,9 +60,17 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
implantName = beacon.Name
|
||||
}
|
||||
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if disableSgn, _ := cmd.Flags().GetBool("disable-sgn"); disableSgn {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
targetArch := config.GetGOARCH()
|
||||
if session != nil && session.Arch != "" {
|
||||
targetArch = session.Arch
|
||||
} else if beacon != nil && beacon.Arch != "" {
|
||||
targetArch = beacon.Arch
|
||||
}
|
||||
|
||||
encoder, err := parseShellcodeEncoderFlag(cmd, targetArch, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
@@ -120,3 +133,118 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Successfully migrated to %d\n", migrate.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeShellcodeEncoderName(name string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(name))
|
||||
normalized = strings.ReplaceAll(normalized, "-", "_")
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizeShellcodeArch(arch string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(arch))
|
||||
switch normalized {
|
||||
case "amd64", "x64", "x86_64":
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//go:build !windows
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGWINCH)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-sigCh:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
signal.Stop(sigCh)
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//go:build windows
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"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
|
||||
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
|
||||
var result string = ""
|
||||
var matchOutput func(a ...interface{}) string
|
||||
var previousPositionEnd int32 = 0
|
||||
|
||||
matchStyle := console.StyleNormal
|
||||
if colorize {
|
||||
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
|
||||
matchStyle = console.StyleBoldRed
|
||||
} else if allowFormatting {
|
||||
matchOutput = color.New(color.Bold).SprintFunc()
|
||||
matchStyle = console.StyleBold
|
||||
}
|
||||
|
||||
for idx, position := range positions {
|
||||
if colorize || allowFormatting {
|
||||
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 {
|
||||
result += line[position.End:]
|
||||
} else {
|
||||
|
||||
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
if burnedOnly && !canary.Triggered {
|
||||
continue
|
||||
}
|
||||
lineColor := console.Normal
|
||||
lineStyle := console.StyleNormal
|
||||
if canary.Triggered {
|
||||
lineColor = console.Bold + console.Red
|
||||
lineStyle = console.StyleBoldRed
|
||||
}
|
||||
firstTrigger := "Never"
|
||||
latestTrigger := "Never"
|
||||
if canary.Triggered {
|
||||
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
|
||||
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
|
||||
firstTrigger = lineStyle.Render(canary.FirstTriggered)
|
||||
latestTrigger = lineStyle.Render(canary.LatestTrigger)
|
||||
}
|
||||
row := table.Row{
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
|
||||
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
|
||||
lineStyle.Render(canary.ImplantName),
|
||||
lineStyle.Render(canary.Domain),
|
||||
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
|
||||
firstTrigger,
|
||||
latestTrigger,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package generate
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -142,12 +143,13 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("profiles", false, profilesGenerateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
|
||||
})
|
||||
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||
@@ -308,7 +310,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("debug-file", "O", "", "path to debug output")
|
||||
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
|
||||
f.StringP("canary", "c", "", "canary domain(s)")
|
||||
|
||||
@@ -341,15 +343,46 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
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("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.Uint32("donut-entropy", 1, "Donut entropy (1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("donut-compress", false, "Enable Donut compression (aplib) (windows shellcode only)")
|
||||
f.Uint32("donut-exitopt", 1, "Donut exit option (1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("donut-bypass", 3, "Donut bypass mode (1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("donut-headers", 1, "Donut headers handling (1=overwrite, 2=keep) (windows shellcode only)")
|
||||
f.Bool("donut-thread", false, "Run unmanaged EXE entrypoint as a new thread (windows shellcode only)")
|
||||
f.Bool("donut-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (windows shellcode only)")
|
||||
f.Uint32("donut-oep", 0, "Donut original entry point override (0=default) (windows shellcode only)")
|
||||
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)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
// - Linux: malasada
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows, macOS, and Linux shellcode)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows shellcode only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows shellcode only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows shellcode only)")
|
||||
|
||||
// Backwards-compatible deprecated Donut flags (hidden).
|
||||
f.Uint32("donut-entropy", 1, "Deprecated (use --shellcode-entropy)")
|
||||
_ = f.MarkDeprecated("donut-entropy", "use --shellcode-entropy")
|
||||
_ = f.MarkHidden("donut-entropy")
|
||||
f.Bool("donut-compress", false, "Deprecated (use --shellcode-compress)")
|
||||
_ = f.MarkDeprecated("donut-compress", "use --shellcode-compress")
|
||||
_ = f.MarkHidden("donut-compress")
|
||||
f.Uint32("donut-exitopt", 1, "Deprecated (use --shellcode-exitopt)")
|
||||
_ = f.MarkDeprecated("donut-exitopt", "use --shellcode-exitopt")
|
||||
_ = f.MarkHidden("donut-exitopt")
|
||||
f.Uint32("donut-bypass", 3, "Deprecated (use --shellcode-bypass)")
|
||||
_ = f.MarkDeprecated("donut-bypass", "use --shellcode-bypass")
|
||||
_ = f.MarkHidden("donut-bypass")
|
||||
f.Uint32("donut-headers", 1, "Deprecated (use --shellcode-headers)")
|
||||
_ = f.MarkDeprecated("donut-headers", "use --shellcode-headers")
|
||||
_ = f.MarkHidden("donut-headers")
|
||||
f.Bool("donut-thread", false, "Deprecated (use --shellcode-thread)")
|
||||
_ = f.MarkDeprecated("donut-thread", "use --shellcode-thread")
|
||||
_ = f.MarkHidden("donut-thread")
|
||||
f.Bool("donut-unicode", false, "Deprecated (use --shellcode-unicode)")
|
||||
_ = f.MarkDeprecated("donut-unicode", "use --shellcode-unicode")
|
||||
_ = f.MarkHidden("donut-unicode")
|
||||
f.Uint32("donut-oep", 0, "Deprecated (use --shellcode-oep)")
|
||||
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
|
||||
_ = f.MarkHidden("donut-oep")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -362,6 +395,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(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)
|
||||
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.Printf("%sCross Compilers%s\n", console.Bold, console.Normal)
|
||||
con.Printf("%s\n", console.StyleBold.Render("Cross Compilers"))
|
||||
for _, cc := range compiler.CrossCompilers {
|
||||
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
|
||||
}
|
||||
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 {
|
||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||
}
|
||||
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 {
|
||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -79,6 +80,7 @@ var (
|
||||
"darwin/arm64": true,
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm64": true,
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
@@ -353,7 +355,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
sgnEnabled := false
|
||||
|
||||
var exports []string
|
||||
exportsArg, _ := cmd.Flags().GetString("exports")
|
||||
@@ -377,8 +378,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
sgnEnabled, _ = cmd.Flags().GetBool("disable-sgn")
|
||||
sgnEnabled = !sgnEnabled
|
||||
case "service":
|
||||
configFormat = clientpb.OutputFormat_SERVICE
|
||||
isService = true
|
||||
@@ -395,9 +394,28 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
if targetOS == "" || targetArch == "" {
|
||||
return "", nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
|
||||
return "", nil
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
||||
switch targetOS {
|
||||
case "windows":
|
||||
// Server-side implementation supports amd64 and 386 only.
|
||||
if targetArch != "amd64" && targetArch != "386" {
|
||||
con.PrintErrorf("Windows shellcode format is only supported on windows/amd64 and windows/386\n")
|
||||
return "", nil
|
||||
}
|
||||
case "darwin":
|
||||
if targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
case "linux":
|
||||
if targetArch != "amd64" && targetArch != "arm64" {
|
||||
con.PrintErrorf("Linux shellcode format is only supported on linux/amd64 and linux/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
default:
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows, macOS, and Linux\n")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
@@ -409,12 +427,19 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
return "", nil
|
||||
}
|
||||
|
||||
donutConfig, err := parseDonutFlags(cmd, targetOS, configFormat, con)
|
||||
shellcodeConfig, err := parseShellcodeFlags(cmd, targetOS, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
shellcodeEncoder, err := parseShellcodeEncoderFlag(cmd, targetArch, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
sgnEnabled := shellcodeEncoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI // legacy field
|
||||
|
||||
var tunIP net.IP
|
||||
if wg, _ := cmd.Flags().GetString("wg"); wg != "" {
|
||||
uniqueWGIP, err := con.Rpc.GenerateUniqueIP(context.Background(), &commonpb.Empty{})
|
||||
@@ -452,6 +477,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
Debug: debug,
|
||||
Evasion: evasion,
|
||||
SGNEnabled: sgnEnabled,
|
||||
ShellcodeEncoder: shellcodeEncoder,
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
@@ -486,23 +512,63 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
|
||||
DebugFile: debugFile,
|
||||
HTTPC2ConfigName: c2Profile,
|
||||
DonutConfig: donutConfig,
|
||||
ShellcodeConfig: shellcodeConfig,
|
||||
}
|
||||
|
||||
return name, config
|
||||
}
|
||||
|
||||
func parseDonutFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.DonutConfig, error) {
|
||||
donutEntropy, _ := cmd.Flags().GetUint32("donut-entropy")
|
||||
donutCompressEnabled, _ := cmd.Flags().GetBool("donut-compress")
|
||||
donutExitOpt, _ := cmd.Flags().GetUint32("donut-exitopt")
|
||||
donutBypass, _ := cmd.Flags().GetUint32("donut-bypass")
|
||||
donutHeaders, _ := cmd.Flags().GetUint32("donut-headers")
|
||||
donutThread, _ := cmd.Flags().GetBool("donut-thread")
|
||||
donutUnicode, _ := cmd.Flags().GetBool("donut-unicode")
|
||||
donutOEP, _ := cmd.Flags().GetUint32("donut-oep")
|
||||
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
|
||||
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
if !cmd.Flags().Changed("shellcode-entropy") {
|
||||
shellcodeEntropy, _ = cmd.Flags().GetUint32("donut-entropy")
|
||||
}
|
||||
|
||||
anyChanged := cmd.Flags().Changed("donut-entropy") ||
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
if !cmd.Flags().Changed("shellcode-compress") {
|
||||
shellcodeCompressEnabled, _ = cmd.Flags().GetBool("donut-compress")
|
||||
}
|
||||
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
if !cmd.Flags().Changed("shellcode-exitopt") {
|
||||
shellcodeExitOpt, _ = cmd.Flags().GetUint32("donut-exitopt")
|
||||
}
|
||||
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
if !cmd.Flags().Changed("shellcode-bypass") {
|
||||
shellcodeBypass, _ = cmd.Flags().GetUint32("donut-bypass")
|
||||
}
|
||||
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
if !cmd.Flags().Changed("shellcode-headers") {
|
||||
shellcodeHeaders, _ = cmd.Flags().GetUint32("donut-headers")
|
||||
}
|
||||
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
if !cmd.Flags().Changed("shellcode-thread") {
|
||||
shellcodeThread, _ = cmd.Flags().GetBool("donut-thread")
|
||||
}
|
||||
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
if !cmd.Flags().Changed("shellcode-unicode") {
|
||||
shellcodeUnicode, _ = cmd.Flags().GetBool("donut-unicode")
|
||||
}
|
||||
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
if !cmd.Flags().Changed("shellcode-oep") {
|
||||
shellcodeOEP, _ = cmd.Flags().GetUint32("donut-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") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-compress") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
@@ -511,43 +577,207 @@ func parseDonutFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
if targetOS != "windows" || configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Donut options only apply to Windows shellcode, ignoring.\n")
|
||||
con.PrintWarnf("Shellcode options only apply when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if donutEntropy < 1 || donutEntropy > 3 {
|
||||
return nil, fmt.Errorf("donut-entropy must be between 1 and 3")
|
||||
}
|
||||
if donutExitOpt < 1 || donutExitOpt > 3 {
|
||||
return nil, fmt.Errorf("donut-exitopt must be between 1 and 3")
|
||||
}
|
||||
if donutBypass < 1 || donutBypass > 3 {
|
||||
return nil, fmt.Errorf("donut-bypass must be between 1 and 3")
|
||||
}
|
||||
if donutHeaders < 1 || donutHeaders > 2 {
|
||||
return nil, fmt.Errorf("donut-headers must be 1 or 2")
|
||||
windowsOnlyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
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") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
cmd.Flags().Changed("donut-headers") ||
|
||||
cmd.Flags().Changed("donut-thread") ||
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
// macOS and Linux shellcode currently only support compression.
|
||||
if targetOS != "windows" {
|
||||
if targetOS != "darwin" && targetOS != "linux" {
|
||||
// parseCompileFlags normally blocks this, but keep a safe fallback.
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows, macOS, and Linux shellcode, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if windowsOnlyChanged {
|
||||
if targetOS == "darwin" {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
|
||||
} else {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on Linux shellcode.\n")
|
||||
}
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Compress: shellcodeCompress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
donutCompress := uint32(1)
|
||||
if donutCompressEnabled {
|
||||
donutCompress = 2
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
return &clientpb.DonutConfig{
|
||||
Entropy: donutEntropy,
|
||||
Compress: donutCompress,
|
||||
ExitOpt: donutExitOpt,
|
||||
Bypass: donutBypass,
|
||||
Headers: donutHeaders,
|
||||
Thread: donutThread,
|
||||
Unicode: donutUnicode,
|
||||
OEP: donutOEP,
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeShellcodeEncoderName(name string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(name))
|
||||
normalized = strings.ReplaceAll(normalized, "-", "_")
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizeShellcodeArch(arch string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(arch))
|
||||
switch normalized {
|
||||
case "amd64", "x64", "x86_64":
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, configFormat clientpb.OutputFormat, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder != "" {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// parseTrafficEncoderArgs - parses the traffic encoder args and returns a bool indicating if traffic encoders are enabled.
|
||||
func parseTrafficEncoderArgs(cmd *cobra.Command, httpC2Enabled bool, con *console.SliverClient) (bool, []*commonpb.File) {
|
||||
trafficEncoders, _ := cmd.Flags().GetString("traffic-encoders")
|
||||
@@ -590,9 +820,7 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
|
||||
|
||||
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
||||
if _, ok := SupportedCompilerTargets[target]; !ok {
|
||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
|
||||
console.Bold, target, console.Normal,
|
||||
)
|
||||
con.Printf("⚠️ Unsupported compiler target %s, but we can try to compile a generic implant.\n", console.StyleBold.Render(target))
|
||||
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
||||
var confirm bool
|
||||
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
|
||||
@@ -942,9 +1170,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)
|
||||
}
|
||||
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 {
|
||||
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()
|
||||
|
||||
@@ -1026,12 +1254,58 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
|
||||
}
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0o700)
|
||||
err = os.WriteFile(saveTo, fileData, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -1049,9 +1323,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)
|
||||
}
|
||||
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 {
|
||||
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()
|
||||
@@ -1078,22 +1352,49 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
if !config.SGNEnabled {
|
||||
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
con.PrintWarnf("Unknown shellcode encoder enum %d, skipping.\n", int32(encoder))
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
con.PrintErrorf("%s\n", msg)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -30,19 +31,49 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE && profile.Config.SGNEnabled {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := profile.Config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && profile.Config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
|
||||
@@ -20,8 +20,10 @@ package generate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -44,9 +46,40 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
}
|
||||
profile := GetImplantProfileByName(name, con)
|
||||
if profile != nil {
|
||||
// If SGN is explicitly disabled, make sure this compilation reflects that despite whatever is set in the profile
|
||||
if SGNDisabled, _ := cmd.Flags().GetBool("disable-sgn"); SGNDisabled {
|
||||
profile.Config.SGNEnabled = !SGNDisabled
|
||||
// Override shellcode encoder if explicitly requested.
|
||||
if cmd.Flags().Changed("shellcode-encoder") {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
if rawEncoder == "" {
|
||||
con.PrintErrorf("shellcode-encoder cannot be empty; use 'none' to disable encoding\n")
|
||||
return
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
profile.Config.ShellcodeEncoder = clientpb.ShellcodeEncoder_NONE
|
||||
profile.Config.SGNEnabled = false
|
||||
} else if profile.Config.Format != clientpb.OutputFormat_SHELLCODE {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
} else {
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to fetch shellcode encoders: %s\n", err)
|
||||
return
|
||||
}
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, profile.Config.GOARCH)
|
||||
if len(compatible) == 0 {
|
||||
con.PrintErrorf("No shellcode encoders are available for arch %s\n", normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
} else {
|
||||
con.PrintErrorf("Unsupported shellcode encoder %q for arch %s (valid: %s)\n", rawEncoder, normalizeShellcodeArch(profile.Config.GOARCH), strings.Join(compatible, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
profile.Config.ShellcodeEncoder = encoder
|
||||
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
}
|
||||
_, err := compile(implantName, profile.Config, save, con)
|
||||
if err != nil {
|
||||
|
||||
@@ -165,10 +165,21 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
|
||||
properties["obsymbols"] = "disabled"
|
||||
}
|
||||
|
||||
if config.SGNEnabled {
|
||||
properties["sgn"] = "enabled"
|
||||
} else {
|
||||
properties["sgn"] = "disabled"
|
||||
properties["shellcodeencoder"] = "n/a"
|
||||
if config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := config.ShellcodeEncoder
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
properties["shellcodeencoder"] = map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_NONE: "none",
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if properties["shellcodeencoder"] == "" {
|
||||
properties["shellcodeencoder"] = fmt.Sprintf("unknown (%d)", int32(encoder))
|
||||
}
|
||||
}
|
||||
|
||||
reconnect := int(config.ReconnectInterval / int64(math.Pow10(9)))
|
||||
@@ -311,8 +322,8 @@ func PrintProfileInfo(name string, con *console.SliverClient) {
|
||||
properties["obsymbols"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Shikata Ga Nai (SGN) is",
|
||||
properties["sgn"],
|
||||
"Shellcode encoder is",
|
||||
properties["shellcodeencoder"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Obfuscation\n")
|
||||
|
||||
@@ -71,7 +71,7 @@ func DisplayTrafficEncoders(encoderMap *clientpb.TrafficEncoderMap, con *console
|
||||
allIDs = append(allIDs, encoder.ID)
|
||||
name := encoder.Wasm.Name
|
||||
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{
|
||||
encoder.ID,
|
||||
@@ -212,9 +212,9 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
||||
for _, test := range tests.Tests {
|
||||
var success string
|
||||
if test.Success {
|
||||
success = console.Bold + console.Green + "Passed" + console.Normal
|
||||
success = console.StyleBoldGreen.Render("Passed")
|
||||
} else {
|
||||
success = console.Bold + console.Red + "Failed!" + console.Normal
|
||||
success = console.StyleBoldRed.Render("Failed!")
|
||||
}
|
||||
errorMsg := "N/A"
|
||||
if test.Err != "" {
|
||||
@@ -234,7 +234,7 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
||||
if running {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@ package help
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,44 +47,45 @@ var (
|
||||
consts.GenerateStr: generateHelp,
|
||||
consts.StageListenerStr: stageListenerHelp,
|
||||
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -135,6 +136,7 @@ var (
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
@@ -145,6 +147,11 @@ FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
|
||||
|
||||
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -182,8 +189,8 @@ You can also stack the C2 configuration with multiple protocols:
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
||||
Supported output formats are Windows PE, Windows DLL, Windows Shellcode, Mach-O, and ELF. The output format is controlled
|
||||
with the --os and --format flags.
|
||||
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
|
||||
Linux ELF, and Linux shellcode (amd64/arm64). The output format is controlled with the --os and --format flags.
|
||||
|
||||
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
|
||||
generate --mtls foo.example.com
|
||||
@@ -199,16 +206,19 @@ To output a Linux ELF executable file, the following command would be used:
|
||||
generate --os linux --mtls foo.example.com
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Donut Shellcode Options ++[[.Normal]]
|
||||
When generating Windows shellcode (--format shellcode), you can tune Donut:
|
||||
--donut-entropy 1|2|3 # Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--donut-compress 0|1 # Compression: 0=off (default), 1=aplib
|
||||
--donut-exitopt 1|2|3 # Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--donut-bypass 1|2|3 # Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
--donut-headers 1|2 # PE headers: 1=overwrite (default), 2=keep
|
||||
--donut-thread 0|1 # Unmanaged EXE: run entrypoint as a new thread (default 0)
|
||||
--donut-unicode 0|1 # Unmanaged DLL: pass Unicode command line (default 0)
|
||||
--donut-oep <uint32> # Override original entry point (0=default)
|
||||
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
|
||||
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
||||
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
|
||||
--shellcode-compress # Enable aPLib compression (windows, macOS, and Linux)
|
||||
--shellcode-entropy 1|2|3 # (windows only) Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--shellcode-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
--shellcode-headers 1|2 # (windows only) PE headers: 1=overwrite (default), 2=keep
|
||||
--shellcode-thread # (windows only) Unmanaged EXE: run entrypoint as a new thread
|
||||
--shellcode-unicode # (windows only) Unmanaged DLL: pass Unicode command line
|
||||
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
|
||||
|
||||
Note: macOS and Linux shellcode currently only support --shellcode-compress; other options are Windows-only.
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||
@@ -1408,24 +1418,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.`
|
||||
)
|
||||
|
||||
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
|
||||
func GetHelpFor(cmdName []string) string {
|
||||
if 0 < len(cmdName) {
|
||||
@@ -1436,34 +1428,100 @@ func GetHelpFor(cmdName []string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FormatHelpTmpl - Applies format template to help string
|
||||
type helpStyleState struct {
|
||||
bold bool
|
||||
underline bool
|
||||
fgSet bool
|
||||
fg lipgloss.Color
|
||||
}
|
||||
|
||||
func (s *helpStyleState) reset() {
|
||||
s.bold = false
|
||||
s.underline = false
|
||||
s.fgSet = false
|
||||
s.fg = ""
|
||||
}
|
||||
|
||||
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 {
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
tmpl, _ := template.New("help").Delims("[[", "]]").Parse(helpStr)
|
||||
tmpl.Execute(outputBuf, struct {
|
||||
Normal string
|
||||
Bold string
|
||||
Underline string
|
||||
Black string
|
||||
Red string
|
||||
Green string
|
||||
Orange string
|
||||
Blue string
|
||||
Purple string
|
||||
Cyan string
|
||||
Gray string
|
||||
}{
|
||||
Normal: normal,
|
||||
Bold: bold,
|
||||
Underline: underline,
|
||||
Black: black,
|
||||
Red: red,
|
||||
Green: green,
|
||||
Orange: orange,
|
||||
Blue: blue,
|
||||
Purple: purple,
|
||||
Cyan: cyan,
|
||||
Gray: gray,
|
||||
})
|
||||
return outputBuf.String()
|
||||
var (
|
||||
out strings.Builder
|
||||
state helpStyleState
|
||||
)
|
||||
state.reset()
|
||||
|
||||
i := 0
|
||||
for i < len(helpStr) {
|
||||
j := strings.Index(helpStr[i:], "[[.")
|
||||
if j < 0 {
|
||||
out.WriteString(state.render(helpStr[i:]))
|
||||
break
|
||||
}
|
||||
j += i
|
||||
if j > i {
|
||||
out.WriteString(state.render(helpStr[i:j]))
|
||||
}
|
||||
|
||||
k := strings.Index(helpStr[j:], "]]")
|
||||
if k < 0 {
|
||||
out.WriteString(state.render(helpStr[j:]))
|
||||
break
|
||||
}
|
||||
token := helpStr[j+3 : j+k]
|
||||
if !state.apply(token) {
|
||||
// Unknown token; preserve it as plain text.
|
||||
out.WriteString(helpStr[j : j+k+2])
|
||||
}
|
||||
i = j + k + 2
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
@@ -61,13 +62,21 @@ type editorModel struct {
|
||||
}
|
||||
|
||||
var (
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
|
||||
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
func modifiedStyle() lipgloss.Style {
|
||||
// Read from the global console theme so theme.yaml changes are reflected.
|
||||
return console.StyleWarning
|
||||
}
|
||||
|
||||
func modifiedCursorStyle() lipgloss.Style {
|
||||
// Read from the global console theme so theme.yaml changes are reflected.
|
||||
return console.StyleWarning.Copy().Reverse(true)
|
||||
}
|
||||
|
||||
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
||||
copyData := append([]byte(nil), data...)
|
||||
model := &editorModel{
|
||||
@@ -400,8 +409,8 @@ func (m *editorModel) renderLine(lineIndex int) string {
|
||||
isModified := m.isModified(idx)
|
||||
if idx == m.cursor {
|
||||
if isModified {
|
||||
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
|
||||
hexCells[i] = modifiedCursorStyle().Render(hexRaw)
|
||||
asciiCells[i] = modifiedCursorStyle().Render(asciiRaw)
|
||||
} else {
|
||||
hexCells[i] = cursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = cursorStyle.Render(asciiRaw)
|
||||
@@ -409,8 +418,8 @@ func (m *editorModel) renderLine(lineIndex int) string {
|
||||
continue
|
||||
}
|
||||
if isModified {
|
||||
hexCells[i] = modifiedStyle.Render(hexRaw)
|
||||
asciiCells[i] = modifiedStyle.Render(asciiRaw)
|
||||
hexCells[i] = modifiedStyle().Render(hexRaw)
|
||||
asciiCells[i] = modifiedStyle().Render(asciiRaw)
|
||||
continue
|
||||
}
|
||||
hexCells[i] = hexRaw
|
||||
|
||||
+38
-38
@@ -70,47 +70,47 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
|
||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, session.Username)
|
||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, session.UID)
|
||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, session.GID)
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, 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(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(" Session ID:"), session.ID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), session.Name)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), session.Hostname)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), session.UUID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), session.Username)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), session.UID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), session.GID)
|
||||
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), session.PID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), session.OS)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), session.Version)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), session.Locale)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), session.Arch)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), session.ActiveC2)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), session.RemoteAddress)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), session.ProxyURL)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render("Reconnect Interval:"), time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(session.FirstContact, 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 {
|
||||
|
||||
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
|
||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, beacon.Username)
|
||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, beacon.UID)
|
||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, beacon.GID)
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, 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(console.Bold+" Last Checkin: %s%s\n", console.Normal, 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(" Beacon ID:"), beacon.ID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), beacon.Name)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), beacon.Hostname)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), beacon.UUID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), beacon.Username)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), beacon.UID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), beacon.GID)
|
||||
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), beacon.PID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), beacon.OS)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), beacon.Version)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), beacon.Locale)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), beacon.Arch)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), beacon.ActiveC2)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), beacon.RemoteAddress)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), beacon.ProxyURL)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Interval:"), time.Duration(beacon.Interval).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Jitter:"), time.Duration(beacon.Jitter).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Next Checkin:"), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
|
||||
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -32,12 +32,13 @@ func WGListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
nport, _ := cmd.Flags().GetUint32("nport")
|
||||
keyExchangePort, _ := cmd.Flags().GetUint32("key-port")
|
||||
|
||||
con.PrintInfof("Starting Wireguard listener ...\n")
|
||||
con.PrintInfof("Starting Wireguard listener (udp:%d, tcp-comms:%d, key-exchange:%d) ...\n", lport, nport, keyExchangePort)
|
||||
wg, err := con.Rpc.StartWGListener(context.Background(), &clientpb.WGListenerReq{
|
||||
Port: lport,
|
||||
NPort: nport,
|
||||
KeyPort: keyExchangePort,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
|
||||
@@ -82,7 +82,7 @@ func PrintLootFile(loot *clientpb.Loot, con *console.SliverClient) {
|
||||
return
|
||||
}
|
||||
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.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
|
||||
|
||||
@@ -20,7 +20,6 @@ package monitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -99,14 +98,12 @@ func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverCli
|
||||
"APIPassword",
|
||||
})
|
||||
|
||||
color := console.Normal
|
||||
|
||||
for _, config := range configs.Providers {
|
||||
row := table.Row{}
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.ID))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.Type))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIKey))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIPassword))
|
||||
row = append(row, config.ID)
|
||||
row = append(row, config.Type)
|
||||
row = append(row, config.APIKey)
|
||||
row = append(row, config.APIPassword)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
|
||||
for index, iface := range interfaces {
|
||||
tw := table.NewWriter()
|
||||
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{
|
||||
{Name: "#", 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) {
|
||||
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
|
||||
ips = append(ips, console.StyleBoldGreen.Render(ip))
|
||||
} else if all {
|
||||
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 {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, srcAddr),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, dstAddr),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.SkState),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, pid),
|
||||
console.StyleGreen.Render(entry.Protocol),
|
||||
console.StyleGreen.Render(srcAddr),
|
||||
console.StyleGreen.Render(dstAddr),
|
||||
console.StyleGreen.Render(entry.SkState),
|
||||
console.StyleGreen.Render(pid),
|
||||
})
|
||||
} else {
|
||||
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 {
|
||||
tw.AppendRow(table.Row{
|
||||
console.Bold + operator.Name + console.Normal,
|
||||
console.StyleBold.Render(operator.Name),
|
||||
status(operator.Online),
|
||||
})
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
|
||||
|
||||
func status(isOnline bool) string {
|
||||
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.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.AppendHeader(table.Row{
|
||||
"ID",
|
||||
|
||||
+107
-107
@@ -36,77 +36,77 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// Stylizes known processes in the `ps` command
|
||||
var knownSecurityTools = map[string][]string{
|
||||
// Process Name -> [Color, Stylized Name]
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"elastic-agent.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
||||
"elastic-endpoint.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
||||
"filebeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - log shipper
|
||||
"metricbeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - metric shipper
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
|
||||
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender (Service Executable)
|
||||
"NisSrv.exe": {console.Red, "Windows Defender"}, // Windows Defender (Network Realtime Inspection)
|
||||
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseNdr.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Network Detection and Response)
|
||||
"SenseSC.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Screenshot Capture Module)
|
||||
"SenseCE.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Classification Engine Module)
|
||||
"SenseCM.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Configuration Management Module)
|
||||
"SenseSampleUploader.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Sample Uploader Module)
|
||||
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Communication Module)
|
||||
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Service Executable)
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdagent.exe": {console.Red, "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)
|
||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
||||
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
|
||||
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
|
||||
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
|
||||
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
|
||||
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
|
||||
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
|
||||
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
|
||||
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
|
||||
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
|
||||
// Known security tools shown by `ps`/`pstree`.
|
||||
// Process executable -> product name.
|
||||
var knownSecurityTools = map[string]string{
|
||||
"ccSvcHst.exe": "Symantec Endpoint Protection", // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": "Carbon Black", // Carbon Black
|
||||
"RepMgr.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"elastic-agent.exe": "Elastic Agent", // Elastic Agent
|
||||
"elastic-endpoint.exe": "Elastic Agent", // Elastic Agent
|
||||
"filebeat.exe": "Elastic Agent", // Elastic Agent - log shipper
|
||||
"metricbeat.exe": "Elastic Agent", // Elastic Agent - metric shipper
|
||||
"smartscreen.exe": "Windows Smart Screen", // Windows Defender Smart Screen
|
||||
"MpCmdRun.exe": "Windows Defender", // Windows Defender Command-line
|
||||
"MonitoringHost.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||
"HealthService.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||
"MsMpEng.exe": "Windows Defender", // Windows Defender (Service Executable)
|
||||
"NisSrv.exe": "Windows Defender", // Windows Defender (Network Realtime Inspection)
|
||||
"SenseIR.exe": "Windows Defender MDE", // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseNdr.exe": "Windows Defender MDE", // Windows Defender Endpoint (Network Detection and Response)
|
||||
"SenseSC.exe": "Windows Defender MDE", // Windows Defender Endpoint (Screenshot Capture Module)
|
||||
"SenseCE.exe": "Windows Defender MDE", // Windows Defender Endpoint (Classification Engine Module)
|
||||
"SenseCM.exe": "Windows Defender MDE", // Windows Defender Endpoint (Configuration Management Module)
|
||||
"SenseSampleUploader.exe": "Windows Defender MDE", // Windows Defender Endpoint (Sample Uploader Module)
|
||||
"SenseCncProxy.exe": "Windows Defender MDE", // Windows Defender Endpoint (Communication Module)
|
||||
"MsSense.exe": "Windows Defender MDE", // Windows Defender Endpoint (Service Executable)
|
||||
"CSFalconService.exe": "CrowdStrike", // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": "CrowdStrike", // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||
"bdagent.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||
"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": "Trend Micro", // TM Deep Security Manager
|
||||
"coreServiceShell.exe": "Trend Micro", // TM Anti-malware scan process
|
||||
"ds_monitor.exe": "Trend Micro", // TM Deep Security Monitor
|
||||
"Notifier.exe": "Trend Micro", // TM Deep Security Notifier's process
|
||||
"dsa.exe": "Trend Micro", // TM Agent's main process
|
||||
"ds_nuagent.exe": "Trend Micro", // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": "Trend Micro", // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelStaticEngine.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelAgent.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelAgentWorker.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelHelperService.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelUI.exe": "SentinelOne", // Sentinel One
|
||||
"Sysmon.exe": "Sysmon", // Sysmon
|
||||
"Sysmon64.exe": "Sysmon64", // Sysmon64
|
||||
"CylanceSvc.exe": "Cylance", // Cylance
|
||||
"CylanceUI.exe": "Cylance", // Cylance
|
||||
"TaniumClient.exe": "Tanium", // Tanium
|
||||
"TaniumCX.exe": "Tanium", // Tanium
|
||||
"TaniumDetectEngine.exe": "Tanium", // Tanium
|
||||
"collector.exe": "Rapid 7 Collector", // Rapid 7 Insight Platform Collector
|
||||
"ir_agent.exe": "Rapid 7 Insight Agent", // Rapid 7 Insight Agent
|
||||
"eguiproxy.exe": "ESET Security", // ESET Internet Security
|
||||
"ekrn.exe": "ESET Security", // ESET Internet Security
|
||||
"efwd.exe": "ESET Security", // ESET Internet Security
|
||||
"AmSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CrAmTray.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"cortex-xdr-payload.exe": "Palo Alto Cortex", // Cortex XDR - offline triage
|
||||
"cysandbox.exe": "Palo Alto Cortex", // Cortex XDR - sandbox
|
||||
"cyuserservice.exe": "Palo Alto Cortex", // Cortex XDR - user service
|
||||
"cywscsvc.exe": "Palo Alto Cortex", // Cortex XDR - security center service
|
||||
"tlaworker.exe": "Palo Alto Cortex", // Cortex XDR - local analysis worker
|
||||
"AEEngine.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - security service
|
||||
"Antiexecutable.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - gui and tray icon
|
||||
}
|
||||
|
||||
// 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 {
|
||||
uniqProducts := map[string]string{}
|
||||
uniqProducts := map[string]struct{}{}
|
||||
for _, proc := range ps.Processes {
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
uniqProducts[secTool[1]] = secTool[0]
|
||||
if product, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
uniqProducts[product] = struct{}{}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
|
||||
color := console.Normal
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
style := console.StyleNormal
|
||||
if _, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
style = console.StyleRed
|
||||
}
|
||||
if session != nil && proc.Pid == session.PID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if beacon != nil && proc.Pid == beacon.PID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
|
||||
var row table.Row
|
||||
@@ -303,28 +303,28 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
||||
args = proc.Executable
|
||||
}
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(args),
|
||||
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||
}
|
||||
} else {
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,26 +341,26 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
||||
args = proc.Executable
|
||||
}
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(args),
|
||||
}
|
||||
} else {
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package processes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/xlab/treeprint"
|
||||
@@ -108,14 +107,14 @@ func (t *PsTree) AddProcess(proc *commonpb.Process) {
|
||||
}
|
||||
|
||||
func (t *PsTree) filterProc(proc *commonpb.Process) string {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
if proc.Pid == t.implantPID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
if _, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
style = console.StyleRed
|
||||
}
|
||||
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
|
||||
return style.Render(proc.Executable)
|
||||
}
|
||||
|
||||
func (t *PsTree) String() string {
|
||||
|
||||
@@ -19,7 +19,6 @@ package reaction
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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) {
|
||||
tw := table.NewWriter()
|
||||
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()
|
||||
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
|
||||
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-jitter", "j", "", "beacon callback jitter (random up to)")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
f.StringP("c2-uri", "c", "", "C2 URI to switch to")
|
||||
})
|
||||
|
||||
renameCmd := &cobra.Command{
|
||||
|
||||
@@ -20,6 +20,7 @@ package reconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -52,6 +53,7 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var beaconJitter time.Duration
|
||||
binterval, _ := cmd.Flags().GetString("beacon-interval")
|
||||
bjitter, _ := cmd.Flags().GetString("beacon-jitter")
|
||||
C2URI, _ := cmd.Flags().GetString("c2-uri")
|
||||
|
||||
if beacon != nil {
|
||||
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{
|
||||
ReconnectInterval: int64(reconnectInterval),
|
||||
BeaconInterval: int64(beaconInterval),
|
||||
BeaconJitter: int64(beaconJitter),
|
||||
C2URI: C2URI,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/monitor"
|
||||
"github.com/bishopfox/sliver/client/command/operators"
|
||||
"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/settings"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
@@ -97,6 +98,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
// Core
|
||||
bind(consts.GenericHelpGroup,
|
||||
exit.Command,
|
||||
serverctx.Commands,
|
||||
licenses.Commands,
|
||||
settings.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"
|
||||
|
||||
"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/charmbracelet/lipgloss"
|
||||
"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 {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
if con.ActiveTarget.GetSession() != nil && con.ActiveTarget.GetSession().ID == session.ID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
var SessionHealth string
|
||||
if session.IsDead {
|
||||
SessionHealth = console.Bold + console.Red + "[DEAD]" + console.Normal
|
||||
SessionHealth = console.StyleBoldRed.Render("[DEAD]")
|
||||
} else {
|
||||
SessionHealth = console.Bold + console.Green + "[ALIVE]" + console.Normal
|
||||
SessionHealth = console.StyleBoldGreen.Render("[ALIVE]")
|
||||
}
|
||||
burned := ""
|
||||
if session.Burned {
|
||||
@@ -211,33 +211,33 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
||||
var rowEntries []string
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, session.Filename, session.PID),
|
||||
style.Render(ShortSessionID(session.ID)),
|
||||
style.Render(session.Name),
|
||||
style.Render(session.Transport),
|
||||
style.Render(session.RemoteAddress),
|
||||
style.Render(session.Hostname),
|
||||
style.Render(username),
|
||||
style.Render(fmt.Sprintf("%s (%d)", session.Filename, session.PID)),
|
||||
}
|
||||
|
||||
if windowsSessionInList {
|
||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, session.Integrity))
|
||||
rowEntries = append(rowEntries, style.Render(session.Integrity))
|
||||
}
|
||||
|
||||
rowEntries = append(rowEntries, []string{
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
|
||||
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
|
||||
style.Render(session.Locale),
|
||||
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
|
||||
burned + SessionHealth,
|
||||
}...)
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
style.Render(ShortSessionID(session.ID)),
|
||||
style.Render(session.Transport),
|
||||
style.Render(session.RemoteAddress),
|
||||
style.Render(session.Hostname),
|
||||
style.Render(username),
|
||||
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
|
||||
burned + SessionHealth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"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)
|
||||
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.SetStyle(GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
|
||||
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 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"})
|
||||
@@ -221,6 +232,7 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
|
||||
if width < 1 {
|
||||
return errors.New("small terminal width must be 1 or greater")
|
||||
}
|
||||
promptStyle := assets.NormalizePromptStyle(result.PromptStyle)
|
||||
|
||||
settings.TableStyle = result.TableStyle
|
||||
settings.AutoAdult = result.AutoAdult
|
||||
@@ -230,5 +242,6 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
|
||||
settings.VimMode = result.VimMode
|
||||
settings.UserConnect = result.UserConnect
|
||||
settings.ConsoleLogs = result.ConsoleLogs
|
||||
settings.PromptStyle = promptStyle
|
||||
return nil
|
||||
}
|
||||
@@ -218,12 +218,12 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
|
||||
}
|
||||
con.Println()
|
||||
} 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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ Implements the 'shell' command group for the Sliver client console. Handlers map
|
||||
|
||||
## 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_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/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"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")
|
||||
})
|
||||
|
||||
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}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//go:build !windows
|
||||
|
||||
package shell
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGWINCH)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-sigCh:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
signal.Stop(sigCh)
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//go:build windows
|
||||
|
||||
package shell
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
+350
-68
@@ -19,24 +19,34 @@ package shell
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
windows = "windows"
|
||||
darwin = "darwin"
|
||||
linux = "linux"
|
||||
darwin = "darwin"
|
||||
linux = "linux"
|
||||
)
|
||||
|
||||
type shellResult int
|
||||
|
||||
const (
|
||||
shellExited shellResult = iota
|
||||
shellDetached
|
||||
shellAttachFailed
|
||||
)
|
||||
|
||||
// ShellCmd - Start an interactive shell on the remote system.
|
||||
@@ -55,92 +65,364 @@ func ShellCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != 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.PrintInfof("Wait approximately 10 seconds after exit, and press <enter> to continue\n")
|
||||
con.PrintInfof("Opening shell tunnel (EOF to exit) ...\n\n")
|
||||
con.PrintInfof("Shell management: `shell ls`, `shell attach <id>`\n")
|
||||
con.PrintInfof("Escape: press Ctrl-] to return to the Sliver client\n")
|
||||
|
||||
session := con.ActiveTarget.GetSession()
|
||||
if session == nil {
|
||||
return
|
||||
return shellAttachFailed
|
||||
}
|
||||
|
||||
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
|
||||
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
|
||||
|
||||
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
|
||||
SessionID: session.ID,
|
||||
})
|
||||
defer cancelTunnel()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
if existing == nil {
|
||||
con.PrintInfof("Opening shell tunnel ...\n\n")
|
||||
} else {
|
||||
con.PrintInfof("Re-attaching to shell %d ...\n\n", existing.ID)
|
||||
}
|
||||
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 (
|
||||
tunnel *core.TunnelIO
|
||||
managed *managedShell
|
||||
enablePTY bool
|
||||
oldState *term.State
|
||||
stateSaved bool
|
||||
stopPtySize = func() {}
|
||||
err error
|
||||
)
|
||||
|
||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: shellPath,
|
||||
EnablePTY: !noPty,
|
||||
TunnelID: tunnel.ID,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
//
|
||||
if shell.Response != nil && shell.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", shell.Response.Err)
|
||||
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
|
||||
TunnelID: tunnel.ID,
|
||||
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())
|
||||
defer cancelTunnel()
|
||||
|
||||
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
|
||||
SessionID: session.ID,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("RPC Error: %s\n", err)
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return shellAttachFailed
|
||||
}
|
||||
return
|
||||
}
|
||||
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)
|
||||
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
|
||||
|
||||
var oldState *term.State
|
||||
if !noPty {
|
||||
// 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{
|
||||
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)
|
||||
log.Printf("Saving terminal state: %v", oldState)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save terminal state")
|
||||
return
|
||||
con.PrintErrorf("Failed to save terminal state\n")
|
||||
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)
|
||||
}()
|
||||
}
|
||||
|
||||
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)
|
||||
detached, _ := runAttachedIO(tunnel, con)
|
||||
if detached {
|
||||
managed.SetOutput(io.Discard)
|
||||
managed.setState(shellStateDetached)
|
||||
return shellDetached
|
||||
}
|
||||
|
||||
if !noPty {
|
||||
log.Printf("Restoring terminal state ...")
|
||||
term.Restore(0, oldState)
|
||||
}
|
||||
managed.SetOutput(io.Discard)
|
||||
managed.setState(shellStateClosing)
|
||||
shells.Remove(managed.ID)
|
||||
|
||||
log.Printf("Exit interactive")
|
||||
bufio.NewWriter(os.Stdout).Flush()
|
||||
// "exit" should return immediately; tunnel close/cleanup is best-effort in the background.
|
||||
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 + ")"
|
||||
}
|
||||
@@ -30,6 +30,8 @@ func normalizeShellcodeArch(arch string) string {
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
|
||||
session.Username,
|
||||
fmt.Sprintf("%s/%s", session.OS, session.Arch),
|
||||
)
|
||||
fmt.Fprintf(table, option+"\n")
|
||||
fmt.Fprintln(table, option)
|
||||
o := strings.ReplaceAll(option, "\t", "")
|
||||
sessionOptionMap[o] = session
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
|
||||
beacon.Username,
|
||||
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
|
||||
)
|
||||
fmt.Fprintf(table, option+"\n")
|
||||
fmt.Fprintln(table, option)
|
||||
o := strings.ReplaceAll(option, "\t", "")
|
||||
beaconOptionMap[o] = beacon
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func filterTasksByTaskType(taskType string, tasks []*clientpb.BeaconTask) []*cli
|
||||
func PrintTask(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
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.AppendRow(table.Row{"State", emojiState(task.State) + " " + prettyState(strings.Title(task.State))})
|
||||
tw.AppendRow(table.Row{"Description", task.Description})
|
||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("output", "o", execReq.Output, "capture command output")
|
||||
f.Bool("background", execReq.Background, "start the process in the background and track it")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||
|
||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||
execCmd.Flags().AddFlagSet(f)
|
||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
|
||||
exec.PrintExecute(execResult, execCmd, con)
|
||||
|
||||
case sliverpb.MsgExecuteChildrenReq:
|
||||
execChildren := &sliverpb.ExecuteChildren{}
|
||||
err := proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
exec.PrintExecuteChildren(execChildren, con)
|
||||
|
||||
case sliverpb.MsgSideloadReq:
|
||||
sideload := &sliverpb.Sideload{}
|
||||
err := proto.Unmarshal(task.Response, sideload)
|
||||
|
||||
@@ -93,13 +93,13 @@ func PrintBeaconTasks(tasks []*clientpb.BeaconTask, cmd *cobra.Command, con *con
|
||||
func prettyState(state string) string {
|
||||
switch strings.ToLower(state) {
|
||||
case "pending":
|
||||
return console.Bold + state + console.Normal
|
||||
return console.StyleBold.Render(state)
|
||||
case "sent":
|
||||
return console.Bold + console.Orange + state + console.Normal
|
||||
return console.StyleBoldOrange.Render(state)
|
||||
case "completed":
|
||||
return console.Bold + console.Green + state + console.Normal
|
||||
return console.StyleBoldGreen.Render(state)
|
||||
case "canceled":
|
||||
return console.Bold + console.Gray + state + console.Normal
|
||||
return console.StyleBoldGray.Render(state)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ func WasmLsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
cacheLine = " (cached)"
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
@@ -46,6 +47,7 @@ import (
|
||||
"github.com/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slog"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -54,32 +56,10 @@ const (
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
// 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
|
||||
// Terminal control sequences (not "styling").
|
||||
Clearln = "\r\x1b[2K"
|
||||
UpN = "\033[%dA"
|
||||
DownN = "\033[%dB"
|
||||
)
|
||||
|
||||
// Observer - A function to call when the sessions changes.
|
||||
@@ -106,6 +86,21 @@ type SliverClient struct {
|
||||
stdoutPipeWriter *os.File
|
||||
stdoutPipeDone chan struct{}
|
||||
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.
|
||||
@@ -113,6 +108,8 @@ type SliverClient struct {
|
||||
// thus has not started monitoring any server events, or started the application.
|
||||
func NewConsole(isServer bool) *SliverClient {
|
||||
assets.Setup(false, false)
|
||||
_ = theme.LoadAndSetCurrentTheme()
|
||||
ApplyTheme(theme.Current())
|
||||
settings, _ := assets.LoadSettings()
|
||||
|
||||
con := &SliverClient{
|
||||
@@ -126,7 +123,10 @@ func NewConsole(isServer bool) *SliverClient {
|
||||
BeaconTaskCallbacksMutex: &sync.Mutex{},
|
||||
IsServer: isServer,
|
||||
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
|
||||
// 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.
|
||||
// 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.
|
||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||
con.Rpc = rpc
|
||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, grpcConn *grpc.ClientConn, details *ConnectionDetails, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||
con.IsCLI = !run
|
||||
con.serverCmds = serverCmds
|
||||
con.sliverCmds = sliverCmds
|
||||
@@ -188,39 +187,29 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
|
||||
sliver := con.App.Menu(consts.ImplantMenu)
|
||||
sliver.SetCommands(sliverCmds)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
con.applyConnectionHooksOnce()
|
||||
|
||||
// console logger
|
||||
if con.Settings.ConsoleLogs {
|
||||
// Classic logs
|
||||
consoleLog := getConsoleLogFile()
|
||||
consoleLogStream, err := con.ClientLogStream("json")
|
||||
if err != nil {
|
||||
log.Printf("Could not get client json log stream: %s", err)
|
||||
}
|
||||
con.setupLogger(consoleLog, consoleLogStream)
|
||||
con.ensureJSONRemoteWriter()
|
||||
con.setupLogger(consoleLog, con.jsonRemoteWriter)
|
||||
defer consoleLog.Close()
|
||||
|
||||
// Ascii cast sessions (complete terminal interface).
|
||||
asciicastLog := getConsoleAsciicastFile()
|
||||
defer asciicastLog.Close()
|
||||
// Ascii cast sessions (complete terminal interface) are only useful
|
||||
// for the interactive console. In CLI mode they would clobber stdout.
|
||||
if !con.IsCLI {
|
||||
asciicastLog := getConsoleAsciicastFile()
|
||||
defer asciicastLog.Close()
|
||||
|
||||
asciicastStream, err := con.ClientLogStream("asciicast")
|
||||
if err != nil {
|
||||
log.Printf("Could not get client asciicast log stream: %s", err)
|
||||
con.ensureAsciicastRemoteWriter()
|
||||
con.setupAsciicastRecord(asciicastLog, con.asciicastRemoteWriter)
|
||||
}
|
||||
con.setupAsciicastRecord(asciicastLog, asciicastStream)
|
||||
}
|
||||
|
||||
if err := con.SetConnection(rpc, grpcConn, details); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rcScript != "" {
|
||||
@@ -319,10 +308,29 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return nil
|
||||
}
|
||||
|
||||
func (con *SliverClient) startEventLoop() {
|
||||
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
|
||||
func (con *SliverClient) applyConnectionHooksOnce() {
|
||||
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 {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
fmt.Printf("%s%s\n", Warn, err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
@@ -337,7 +345,7 @@ func (con *SliverClient) startEventLoop() {
|
||||
switch event.EventType {
|
||||
|
||||
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)
|
||||
for _, session := range sessions {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
@@ -346,7 +354,7 @@ func (con *SliverClient) startEventLoop() {
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
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)
|
||||
for _, session := range sessions {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
@@ -462,7 +470,7 @@ func (con *SliverClient) triggerReactions(event *clientpb.Event) {
|
||||
|
||||
for _, reaction := range reactions {
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
prompt := Underline + "sliver" + Normal
|
||||
if con.IsServer {
|
||||
prompt = Bold + "[server] " + Normal + Underline + "sliver" + Normal
|
||||
promptStyle := assets.PromptStyleHost
|
||||
if con.Settings != nil {
|
||||
promptStyle = assets.NormalizePromptStyle(con.Settings.PromptStyle)
|
||||
}
|
||||
if con.ActiveTarget.GetSession() != nil {
|
||||
prompt += fmt.Sprintf(Bold+Red+" (%s)%s", con.ActiveTarget.GetSession().Name, Normal)
|
||||
} else if con.ActiveTarget.GetBeacon() != nil {
|
||||
prompt += fmt.Sprintf(Bold+Blue+" (%s)%s", con.ActiveTarget.GetBeacon().Name, Normal)
|
||||
|
||||
if promptStyle == assets.PromptStyleCustom {
|
||||
src := assets.DefaultPromptTemplate
|
||||
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 += " > "
|
||||
return Clearln + prompt
|
||||
}
|
||||
|
||||
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 {
|
||||
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 := ""
|
||||
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)
|
||||
|
||||
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)
|
||||
fmt.Printf("%sServer v%s - %s%s\r\n", Info, serverSemVer, serverVer.Commit, dirty)
|
||||
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")
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -564,7 +638,7 @@ func (con *SliverClient) CheckLastUpdate() {
|
||||
day := 24 * time.Hour
|
||||
if compiledAt.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 {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
fmt.Printf("%s%s\n", Warn, err)
|
||||
return nil
|
||||
}
|
||||
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()
|
||||
}
|
||||
if color {
|
||||
interval = fmt.Sprintf("%s%s%s", Bold+Red, interval, Normal)
|
||||
interval = StyleBoldRed.Render(interval)
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
if color {
|
||||
interval = fmt.Sprintf("%s%s%s", Bold+Green, interval, Normal)
|
||||
interval = StyleBoldGreen.Render(interval)
|
||||
}
|
||||
}
|
||||
return interval
|
||||
@@ -789,7 +863,7 @@ type ActiveTarget struct {
|
||||
// GetSessionInteractive - Get the active target(s).
|
||||
func (s *ActiveTarget) GetInteractive() (*clientpb.Session, *clientpb.Beacon) {
|
||||
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 s.session, s.beacon
|
||||
@@ -803,7 +877,7 @@ func (s *ActiveTarget) Get() (*clientpb.Session, *clientpb.Beacon) {
|
||||
// GetSessionInteractive - GetSessionInteractive the active session.
|
||||
func (s *ActiveTarget) GetSessionInteractive() *clientpb.Session {
|
||||
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 s.session
|
||||
@@ -817,7 +891,7 @@ func (s *ActiveTarget) GetSession() *clientpb.Session {
|
||||
// GetBeaconInteractive - Get beacon interactive the active session.
|
||||
func (s *ActiveTarget) GetBeaconInteractive() *clientpb.Beacon {
|
||||
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 s.beacon
|
||||
@@ -1037,82 +1111,60 @@ var abilities = []string{
|
||||
"jump-start",
|
||||
}
|
||||
|
||||
var asciiLogos = []string{
|
||||
Red + `
|
||||
██████ ██▓ ██▓ ██▒ █▓▓█████ ██▀███
|
||||
▒██ ▒ ▓██▒ ▓██▒▓██░ █▒▓█ ▀ ▓██ ▒ ██▒
|
||||
░ ▓██▄ ▒██░ ▒██▒ ▓██ █▒░▒███ ▓██ ░▄█ ▒
|
||||
▒ ██▒▒██░ ░██░ ▒██ █░░▒▓█ ▄ ▒██▀▀█▄
|
||||
▒██████▒▒░██████▒░██░ ▒▀█░ ░▒████▒░██▓ ▒██▒
|
||||
▒ ▒▓▒ ▒ ░░ ▒░▓ ░░▓ ░ ▐░ ░░ ▒░ ░░ ▒▓ ░▒▓░
|
||||
░ ░▒ ░ ░░ ░ ▒ ░ ▒ ░ ░ ░░ ░ ░ ░ ░▒ ░ ▒░
|
||||
░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░░ ░
|
||||
░ ░ ░ ░ ░ ░ ░ ░
|
||||
` + Normal,
|
||||
type asciiLogoStyle int
|
||||
|
||||
Green + `
|
||||
███████╗██╗ ██╗██╗ ██╗███████╗██████╗
|
||||
██╔════╝██║ ██║██║ ██║██╔════╝██╔══██╗
|
||||
███████╗██║ ██║██║ ██║█████╗ ██████╔╝
|
||||
╚════██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
||||
███████║███████╗██║ ╚████╔╝ ███████╗██║ ██║
|
||||
╚══════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
||||
` + Normal,
|
||||
const (
|
||||
logoStyleRed asciiLogoStyle = iota
|
||||
logoStyleGreen
|
||||
logoStyleBoldGray
|
||||
)
|
||||
|
||||
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|
|
||||
` + "`------'`------'`------'`------'`------'`------'" + `
|
||||
` + Normal,
|
||||
|
||||
Purple + `
|
||||
****@@ @@****
|
||||
@@@@@@***@ @***@@@@@@
|
||||
@%%@@@%%#***@ @***#%%@@@%%@
|
||||
%%%%%##%%%%****@ @****%%%%##%%%%%
|
||||
%%%%#####%%%%#*###@ @#####%%%%#####%%%%
|
||||
@%%%*@#####%%%%@####@ @####@%%%%#####@#%%%@
|
||||
%%%+@**#####%%%%#####@ @#####%%%%#####**@+%%%@
|
||||
#%%=+*+**###%#%%%###### ######%%%%%###**+*+=%%%
|
||||
%#%===++@*####%@@@@###### ######@@@@%####*@++===%%%
|
||||
@#@--===+**%%%@@@@@@#####%% %%#####@@@@@@%%#**+===--@#@
|
||||
#@----%=+*%%%%@@@%%%####%%%@ @ @ @%%%####%%%@@@%%%%*+=%----@#
|
||||
#%----=#++*%%%%@%%%%%@%%%%%%%%%@ @%%%%%%%%%@%%%%%@%%%%*++#=----%#
|
||||
%+----===+**@%%@#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%@**+===----+%
|
||||
@=----==++***@%@#%%%%%%%@%%%%@%%%%%%%%%%%%%%%%%%%%@%%%%@%%%%%%%#@%@***++==-----@
|
||||
-----==+#%**##@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@@##**%#+==-----
|
||||
%----==+**%%%%@@@#@%%%%%%%%%%@%%%%%%%%%%%%%%%%%%@%%%%%%%%%%@#@@@%%%%*++==----%
|
||||
=--#+=+**#@%%####%####%%%%@@@@@%%%%%%##%%%%%%@@@@@@%%%####%####%%@#**+=+#--=
|
||||
+-==+*%%%%########%#######%@@@@@@%%%%%#%%@@@@@@%#######%########%%%%*+==-=
|
||||
-----==++**#%######################%@@@%%%%@@@%######################%#**++==-----
|
||||
=--===+**######@##############%%%%%%%%%%%%%%%%%%%%##############@######**+===--=
|
||||
%-==+%@@@#######%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%#######@@@%+==-%
|
||||
@=@@@@@@@@#%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%#@@@@@@@@=@
|
||||
@@@@@@@@@@@@@%%%%%%%%%%%%@@@@%%%%%%%%**%%%%%%%%@@@@%%%%%%%%%%%%@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@%%#%@@%%%%@@@@@%%%%%@#**#@%%%%%@@@@@%%%%@@%#%%@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@%#####%#%%%%%%%@%###**###%@%%%%%%%#######%@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@*##%%%%%%%%%%%%%##**##%%%%%%%%%%%%###*@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@%%%%%@@@@@@@@@@@@@@@@@@@ %%%%%%%%%%%%%##**##%%%%%%%%%%%%% @@@@@@@@@@@@@@@@@@@%%%%%@
|
||||
@%%%%%%%%%%%%%%@@@@@@@@@@@@ . %%%%%@%%%%%##**##%%%%%@%%%%% @@@@@@@@@@@@%%%%%%%%%%%%%%@
|
||||
%%%%%%%@+. % :@%%%%%%%%@@@@@@@@@%@%%@@@@##**##@@@@%%@%@@@@@@@@@%%%%%%%%@: % ..+@%%%%%%%
|
||||
%#. . : .-@%%%%%@@@@@@@@@@@@@@##**##@@@@@@@@@@@@@@%%%%%@-. : .*%
|
||||
@%%@* #..:--=++*@%@@@@@@@@@@@@##**##@@@@@@@@@@@@@@*++==-:. # *@%%@
|
||||
%%%%%%@..*.:+% ..::+-==++++@-=@@@@@@@@@@@%####%@@@@@@@@@@@=-@++++==-+::.. #+:.*..@%%%%%%
|
||||
@###%.......::%-==++**#@++#*: .:-+@@@@@@@@@@%####%@@@@@@@@@@+-:. .*#++@#**++==-%::.......%###@
|
||||
#%..........:::-==%+*******@* . .:-+@@@@@@@@@@####@@@@@@@@@@+-: #@*******+%=--:::..........%#
|
||||
@@@+:...::::--=++**@***##*==+ :-+@@@.@@@@%%##%%@@@@.@@@+-: ++=*##***@**++=--::::...:+@@@
|
||||
@%%%***###%@#****##@##++++++. .:=*@+ @@@@%%##%%@@@@ =@*=:. .+++++=##@##****#@%###***%%%@
|
||||
@@=====+++++***#####%%%%@++++**#.:=*@ .@@%%####%%@@. @*=:.#***+++@%%%%#####***+++++=====@@
|
||||
@@@#+++++***##%%%%%***##%%*# @@*------*@@ #*%%##***%%%%%##***+++++#@@@
|
||||
@@@@%%%@%%%%%. **--------** .%%%%%%%%%@@@@
|
||||
@%%%% %@@@@@@@@@@@@@ %%%%@
|
||||
@@ @::@@@@@@@@@@::@ @@
|
||||
-::::::@@@@:::::::
|
||||
*-----========-----+
|
||||
@###%%%@@@@%%%###@
|
||||
@%%%%%%@
|
||||
` + Normal,
|
||||
`},
|
||||
}
|
||||
+111
-2
@@ -26,6 +26,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -51,6 +52,109 @@ func (l *ConsoleClientLogger) Write(buf []byte) (int, error) {
|
||||
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
|
||||
// going through this stream in a specific log subdirectory/file.
|
||||
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)
|
||||
|
||||
// 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.
|
||||
@@ -242,7 +351,7 @@ func (con *SliverClient) PrintWarnf(format string, args ...any) {
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type promptPalette struct {
|
||||
Default lipgloss.Color
|
||||
Mods map[int]lipgloss.Color
|
||||
}
|
||||
|
||||
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: lipgloss.Color(p.Default),
|
||||
Mods: map[int]lipgloss.Color{},
|
||||
}
|
||||
for k, v := range p.Mods {
|
||||
out.Mods[k] = lipgloss.Color(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,133 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// 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) lipgloss.Color {
|
||||
if p.Default != "" {
|
||||
return lipgloss.Color(p.Default)
|
||||
}
|
||||
return lipgloss.Color("#ffffff")
|
||||
}
|
||||
paletteMod := func(p theme.Palette, mod int) lipgloss.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("[+] ")
|
||||
}
|
||||
@@ -208,8 +208,9 @@ const (
|
||||
GetGIDStr = "getgid"
|
||||
WhoamiStr = "whoami"
|
||||
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ExecuteChildrenStr = "children"
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import "net"
|
||||
|
||||
// ResetClientState tears down singleton client-side state that is tied to a
|
||||
// specific server connection (tunnels, portfwds, SOCKS, cursed procs, etc).
|
||||
//
|
||||
// This is primarily used to support switching servers inside a single
|
||||
// sliver-client process.
|
||||
func ResetClientState() {
|
||||
// Port forwards
|
||||
for _, meta := range Portfwds.List() {
|
||||
Portfwds.Remove(meta.ID)
|
||||
}
|
||||
|
||||
// SOCKS
|
||||
for _, meta := range SocksProxies.List() {
|
||||
SocksProxies.Remove(meta.ID)
|
||||
}
|
||||
SocksConnPool.Range(func(key, value any) bool {
|
||||
if conn, ok := value.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
SocksConnPool.Delete(key)
|
||||
return true
|
||||
})
|
||||
|
||||
// Tunnels
|
||||
GetTunnels().Reset()
|
||||
|
||||
// Cursed processes
|
||||
CursedProcesses.Range(func(key, value any) bool {
|
||||
if cp, ok := value.(*CursedProcess); ok && cp != nil && cp.PortFwd != nil {
|
||||
Portfwds.Remove(cp.PortFwd.ID)
|
||||
}
|
||||
CursedProcesses.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
@@ -27,21 +28,21 @@ import (
|
||||
)
|
||||
|
||||
// TunnelLoop - Parses incoming tunnel messages and distributes them
|
||||
// to session/tunnel objects
|
||||
// Expected to be called only once during initialization
|
||||
func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
|
||||
//
|
||||
// to session/tunnel objects
|
||||
// Expected to be called only once during initialization
|
||||
func TunnelLoop(ctx context.Context, rpc rpcpb.SliverRPCClient) error {
|
||||
log.Println("Starting tunnel data loop ...")
|
||||
defer log.Printf("Warning: TunnelLoop exited")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
stream, err := rpc.TunnelData(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
GetTunnels().SetStream(stream)
|
||||
defer GetTunnels().SetStream(nil)
|
||||
|
||||
for {
|
||||
log.Printf("Waiting for TunnelData ...")
|
||||
@@ -54,6 +55,9 @@ func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return nil
|
||||
}
|
||||
log.Printf("Tunnel data read error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -160,3 +160,19 @@ func (t *tunnels) CloseForSession(sessionID string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset closes all open tunnels and clears the underlying storage/stream.
|
||||
// This is used when switching servers inside a single client process.
|
||||
func (t *tunnels) Reset() {
|
||||
t.SetStream(nil)
|
||||
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
for tunnelID, tunnel := range *t.tunnels {
|
||||
if tunnel != nil {
|
||||
_ = tunnel.Close()
|
||||
}
|
||||
delete(*t.tunnels, tunnelID)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package forms
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
@@ -40,10 +41,11 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
||||
Title("Select packages or bundles to install").
|
||||
Description("Use space to select and enter to install.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedNames)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forms
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
@@ -39,10 +40,11 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
||||
Title("Select updates to apply").
|
||||
Description("Use space to select and enter to apply.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedIDs)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
@@ -124,6 +125,7 @@ func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
|
||||
),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
@@ -154,6 +155,7 @@ func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult,
|
||||
),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -60,6 +61,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -83,6 +85,8 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
@@ -131,6 +135,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -65,6 +66,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -88,6 +90,8 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
@@ -163,6 +167,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func commonArchOptions(goos string) []huh.Option[string] {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -64,6 +65,10 @@ func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if goos == "darwin" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
} else if goos == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
+18
-12
@@ -2,13 +2,15 @@ package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/huh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const defaultSelectHeight = 10
|
||||
const defaultSelectHeight = 5
|
||||
|
||||
func getTerminalWidth() int {
|
||||
// Try to get actual terminal width
|
||||
@@ -31,7 +33,7 @@ func Confirm(title string, value *bool) error {
|
||||
Title(title).
|
||||
Value(value),
|
||||
),
|
||||
)
|
||||
).WithTheme(theme.HuhTheme())
|
||||
|
||||
return form.Run()
|
||||
}
|
||||
@@ -48,7 +50,7 @@ func Input(title string, value *string) error {
|
||||
Title(title).
|
||||
Value(value),
|
||||
),
|
||||
)
|
||||
).WithTheme(theme.HuhTheme())
|
||||
|
||||
return form.Run()
|
||||
}
|
||||
@@ -66,7 +68,7 @@ func Text(title string, value *string) error {
|
||||
ExternalEditor(false).
|
||||
Value(value),
|
||||
),
|
||||
)
|
||||
).WithTheme(theme.HuhTheme())
|
||||
|
||||
return form.Run()
|
||||
}
|
||||
@@ -93,10 +95,13 @@ func MultiSelect(title string, options []string, value *[]string) error {
|
||||
field := huh.NewMultiSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select/MultiSelect Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
||||
form := huh.NewForm(huh.NewGroup(field)).
|
||||
WithTheme(theme.HuhTheme()).
|
||||
WithWidth(getTerminalWidth())
|
||||
return form.Run()
|
||||
}
|
||||
|
||||
@@ -115,7 +120,8 @@ func selectPrompt(title string, options []string, value *string, required bool)
|
||||
field := huh.NewSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
if required {
|
||||
@@ -127,7 +133,9 @@ func selectPrompt(title string, options []string, value *string, required bool)
|
||||
})
|
||||
}
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
||||
form := huh.NewForm(huh.NewGroup(field)).
|
||||
WithTheme(theme.HuhTheme()).
|
||||
WithWidth(getTerminalWidth())
|
||||
err := form.Run()
|
||||
|
||||
// On error restore the originalValue and return err
|
||||
@@ -147,10 +155,8 @@ func ensureSelectedValue(options []string, value *string) {
|
||||
*value = options[0]
|
||||
return
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == *value {
|
||||
return
|
||||
}
|
||||
if slices.Contains(options, *value) {
|
||||
return
|
||||
}
|
||||
*value = options[0]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
@@ -19,6 +20,7 @@ type SettingsFormResult struct {
|
||||
VimMode bool
|
||||
UserConnect bool
|
||||
ConsoleLogs bool
|
||||
PromptStyle string
|
||||
}
|
||||
|
||||
// SettingsForm prompts for client settings and returns the collected values.
|
||||
@@ -39,10 +41,14 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
|
||||
VimMode: settings.VimMode,
|
||||
UserConnect: settings.UserConnect,
|
||||
ConsoleLogs: settings.ConsoleLogs,
|
||||
PromptStyle: assets.NormalizePromptStyle(settings.PromptStyle),
|
||||
}
|
||||
if result.TableStyle == "" {
|
||||
result.TableStyle = tableStyleOptions[0]
|
||||
}
|
||||
if result.PromptStyle == "" {
|
||||
result.PromptStyle = assets.PromptStyleHost
|
||||
}
|
||||
|
||||
styleOptions := make([]huh.Option[string], 0, len(tableStyleOptions))
|
||||
for _, option := range tableStyleOptions {
|
||||
@@ -51,6 +57,17 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Title("Prompt style").
|
||||
Description("Customize the sliver prompt prefix").
|
||||
Options(
|
||||
huh.NewOption(assets.PromptStyleHost, assets.PromptStyleHost),
|
||||
huh.NewOption(assets.PromptStyleOperatorHost, assets.PromptStyleOperatorHost),
|
||||
huh.NewOption(assets.PromptStyleBasic, assets.PromptStyleBasic),
|
||||
huh.NewOption(assets.PromptStyleCustom, assets.PromptStyleCustom),
|
||||
).
|
||||
Height(4).
|
||||
Value(&result.PromptStyle),
|
||||
huh.NewSelect[string]().
|
||||
Title("Table style").
|
||||
Options(styleOptions...).
|
||||
@@ -90,6 +107,7 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
|
||||
).Title("Behavior"),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+18
-17
@@ -1,5 +1,23 @@
|
||||
package packages
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -10,23 +28,6 @@ import (
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 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/>.
|
||||
*/
|
||||
const (
|
||||
KeyValueSchemaName = "key-value"
|
||||
GroupedKeyValueSchemaName = "grouped-key-value"
|
||||
|
||||
+2
-15
@@ -10,21 +10,8 @@ import (
|
||||
)
|
||||
|
||||
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"
|
||||
// Terminal control sequence for clearing the current line.
|
||||
clearln = "\r\x1b[2K"
|
||||
)
|
||||
|
||||
// Spinner types.
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package theme
|
||||
|
||||
// DefaultThemeYAML is the initial theme created at <client app dir>/theme.yaml.
|
||||
//
|
||||
// Palettes:
|
||||
// - default: neutral base
|
||||
// - primary/secondary: accent colors
|
||||
// - success/warning/danger: semantic colors
|
||||
//
|
||||
// Each palette has a "default" value and a set of numeric modifiers:
|
||||
// 50, 100, 200, 300, 400, 500, 600, 700, 800, 900.
|
||||
const DefaultThemeYAML = `# Sliver client console theme.
|
||||
# Colors are hex strings (e.g. "#006FEE").
|
||||
|
||||
primary:
|
||||
default: "#006FEE"
|
||||
mods:
|
||||
50: "#001731"
|
||||
100: "#002e62"
|
||||
200: "#004493"
|
||||
300: "#005bc4"
|
||||
400: "#006FEE"
|
||||
500: "#338ef7"
|
||||
600: "#66aaf9"
|
||||
700: "#99c7fb"
|
||||
800: "#cce3fd"
|
||||
900: "#e6f1fe"
|
||||
|
||||
secondary:
|
||||
default: "#9353d3"
|
||||
mods:
|
||||
50: "#180828"
|
||||
100: "#301050"
|
||||
200: "#481878"
|
||||
300: "#6020a0"
|
||||
400: "#7828c8"
|
||||
500: "#9353d3"
|
||||
600: "#ae7ede"
|
||||
700: "#c9a9e9"
|
||||
800: "#e4d4f4"
|
||||
900: "#f2eafa"
|
||||
|
||||
default:
|
||||
default: "#3f3f46"
|
||||
mods:
|
||||
50: "#18181b"
|
||||
100: "#27272a"
|
||||
200: "#3f3f46"
|
||||
300: "#52525b"
|
||||
400: "#71717a"
|
||||
500: "#a1a1aa"
|
||||
600: "#d4d4d8"
|
||||
700: "#e4e4e7"
|
||||
800: "#f4f4f5"
|
||||
900: "#fafafa"
|
||||
|
||||
success:
|
||||
default: "#17c964"
|
||||
mods:
|
||||
50: "#052814"
|
||||
100: "#095028"
|
||||
200: "#0e793c"
|
||||
300: "#12a150"
|
||||
400: "#17c964"
|
||||
500: "#45d483"
|
||||
600: "#74dfa2"
|
||||
700: "#a2e9c1"
|
||||
800: "#d1f4e0"
|
||||
900: "#e8faf0"
|
||||
|
||||
warning:
|
||||
default: "#f5a524"
|
||||
mods:
|
||||
50: "#312107"
|
||||
100: "#62420e"
|
||||
200: "#936316"
|
||||
300: "#c4841d"
|
||||
400: "#f5a524"
|
||||
500: "#f7b750"
|
||||
600: "#f9c97c"
|
||||
700: "#fbdba7"
|
||||
800: "#fdedd3"
|
||||
900: "#fefce8"
|
||||
|
||||
danger:
|
||||
default: "#f31260"
|
||||
mods:
|
||||
50: "#310413"
|
||||
100: "#610726"
|
||||
200: "#920b3a"
|
||||
300: "#c20e4d"
|
||||
400: "#f31260"
|
||||
500: "#f54180"
|
||||
600: "#f871a0"
|
||||
700: "#faa0bf"
|
||||
800: "#fdd0df"
|
||||
900: "#fee7ef"
|
||||
`
|
||||
@@ -0,0 +1,67 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// HuhTheme returns a huh Theme derived from the current Sliver client theme.
|
||||
//
|
||||
// This is used for all interactive TUI forms in the client so that colors are
|
||||
// consistent with the rest of the console styling (theme.yaml).
|
||||
func HuhTheme() *huh.Theme {
|
||||
t := huh.ThemeBase()
|
||||
|
||||
border := DefaultMod(300)
|
||||
text := DefaultMod(900)
|
||||
dim := DefaultMod(500)
|
||||
faint := DefaultMod(400)
|
||||
|
||||
primary := Primary()
|
||||
accent := Secondary()
|
||||
|
||||
success := Success()
|
||||
danger := Danger()
|
||||
|
||||
// Focused styles.
|
||||
t.Focused.Base = t.Focused.Base.BorderForeground(border)
|
||||
t.Focused.Card = t.Focused.Base
|
||||
t.Focused.Title = t.Focused.Title.Foreground(primary).Bold(true)
|
||||
t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(primary).Bold(true).MarginBottom(1)
|
||||
t.Focused.Description = t.Focused.Description.Foreground(dim)
|
||||
t.Focused.Directory = t.Focused.Directory.Foreground(primary)
|
||||
t.Focused.File = t.Focused.File.Foreground(text)
|
||||
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(danger)
|
||||
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(danger)
|
||||
t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(accent)
|
||||
t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(accent)
|
||||
t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(accent)
|
||||
t.Focused.Option = t.Focused.Option.Foreground(text)
|
||||
t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(accent)
|
||||
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(success)
|
||||
t.Focused.SelectedPrefix = t.Focused.SelectedPrefix.Foreground(success)
|
||||
t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(text)
|
||||
t.Focused.UnselectedPrefix = t.Focused.UnselectedPrefix.Foreground(faint)
|
||||
|
||||
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(text).Background(primary)
|
||||
t.Focused.Next = t.Focused.FocusedButton
|
||||
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(text).Background(lipgloss.Color(DefaultMod(100)))
|
||||
|
||||
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primary)
|
||||
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(faint)
|
||||
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(accent)
|
||||
|
||||
// Blurred styles (same palette, but without the focused border/indicators).
|
||||
t.Blurred = t.Focused
|
||||
t.Blurred.Base = t.Blurred.Base.BorderStyle(lipgloss.HiddenBorder())
|
||||
t.Blurred.Card = t.Blurred.Base
|
||||
t.Blurred.MultiSelectSelector = lipgloss.NewStyle().SetString(" ")
|
||||
t.Blurred.NextIndicator = lipgloss.NewStyle()
|
||||
t.Blurred.PrevIndicator = lipgloss.NewStyle()
|
||||
|
||||
// Group styles.
|
||||
t.Group.Title = t.Focused.Title
|
||||
t.Group.Description = t.Focused.Description
|
||||
|
||||
return t
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ThemeFileName = "theme.yaml"
|
||||
)
|
||||
|
||||
type Palette struct {
|
||||
Default string `yaml:"default"`
|
||||
Mods map[int]string `yaml:"mods"`
|
||||
}
|
||||
|
||||
type Theme struct {
|
||||
Primary Palette `yaml:"primary"`
|
||||
Secondary Palette `yaml:"secondary"`
|
||||
Default Palette `yaml:"default"`
|
||||
Success Palette `yaml:"success"`
|
||||
Warning Palette `yaml:"warning"`
|
||||
Danger Palette `yaml:"danger"`
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
current Theme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Ensure there is always a usable theme without I/O.
|
||||
t, err := parseThemeYAML([]byte(DefaultThemeYAML))
|
||||
if err == nil {
|
||||
current = t
|
||||
} else {
|
||||
// Minimal fallback; should never happen unless DefaultThemeYAML is corrupted.
|
||||
current = Theme{
|
||||
Primary: Palette{Default: "#006FEE", Mods: map[int]string{}},
|
||||
Default: Palette{Default: "#ffffff", Mods: map[int]string{}},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ThemePath() string {
|
||||
return filepath.Join(assets.GetRootAppDir(), ThemeFileName)
|
||||
}
|
||||
|
||||
func SetCurrentTheme(t Theme) {
|
||||
mu.Lock()
|
||||
current = t
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func EnsureThemeFile() error {
|
||||
p := ThemePath()
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return nil
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p, []byte(DefaultThemeYAML), 0o600)
|
||||
}
|
||||
|
||||
func LoadThemeFromDisk() (Theme, error) {
|
||||
data, err := os.ReadFile(ThemePath())
|
||||
if err != nil {
|
||||
return Theme{}, err
|
||||
}
|
||||
return parseThemeYAML(data)
|
||||
}
|
||||
|
||||
func LoadAndSetCurrentTheme() error {
|
||||
_ = EnsureThemeFile()
|
||||
t, err := LoadThemeFromDisk()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
SetCurrentTheme(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseThemeYAML(data []byte) (Theme, error) {
|
||||
var t Theme
|
||||
if err := yaml.Unmarshal(data, &t); err != nil {
|
||||
return Theme{}, err
|
||||
}
|
||||
normalizePalette(&t.Primary)
|
||||
normalizePalette(&t.Secondary)
|
||||
normalizePalette(&t.Default)
|
||||
normalizePalette(&t.Success)
|
||||
normalizePalette(&t.Warning)
|
||||
normalizePalette(&t.Danger)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func normalizePalette(p *Palette) {
|
||||
if p.Mods == nil {
|
||||
p.Mods = map[int]string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (t Theme) paletteColor(p Palette) lipgloss.Color {
|
||||
if p.Default != "" {
|
||||
return lipgloss.Color(p.Default)
|
||||
}
|
||||
// Defensive fallback.
|
||||
return lipgloss.Color("#ffffff")
|
||||
}
|
||||
|
||||
func (t Theme) paletteMod(p Palette, mod int) lipgloss.Color {
|
||||
if mod != 0 {
|
||||
if v, ok := p.Mods[mod]; ok && v != "" {
|
||||
return lipgloss.Color(v)
|
||||
}
|
||||
}
|
||||
return t.paletteColor(p)
|
||||
}
|
||||
|
||||
// Primary returns the theme primary color.
|
||||
func Primary() lipgloss.Color { return Current().paletteColor(Current().Primary) }
|
||||
func PrimaryMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Primary, mod)
|
||||
}
|
||||
|
||||
// Secondary returns the theme secondary color.
|
||||
func Secondary() lipgloss.Color { return Current().paletteColor(Current().Secondary) }
|
||||
func SecondaryMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Secondary, mod)
|
||||
}
|
||||
|
||||
// Default returns the theme default/neutral color.
|
||||
func Default() lipgloss.Color { return Current().paletteColor(Current().Default) }
|
||||
func DefaultMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Default, mod)
|
||||
}
|
||||
|
||||
// Success returns the theme success color.
|
||||
func Success() lipgloss.Color { return Current().paletteColor(Current().Success) }
|
||||
func SuccessMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Success, mod)
|
||||
}
|
||||
|
||||
// Warning returns the theme warning color.
|
||||
func Warning() lipgloss.Color { return Current().paletteColor(Current().Warning) }
|
||||
func WarningMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Warning, mod)
|
||||
}
|
||||
|
||||
// Danger returns the theme danger color.
|
||||
func Danger() lipgloss.Color { return Current().paletteColor(Current().Danger) }
|
||||
func DangerMod(mod int) lipgloss.Color {
|
||||
t := Current()
|
||||
return t.paletteMod(t.Danger, mod)
|
||||
}
|
||||
|
||||
// Current returns the in-memory current theme (defaults to DefaultThemeYAML).
|
||||
func Current() Theme {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return current
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaMu sync.RWMutex
|
||||
kaParams *keepalive.ClientParameters
|
||||
)
|
||||
|
||||
// SetKeepaliveParams configures gRPC keepalive pings for future MTLSConnect dials.
|
||||
// Behavior is unchanged unless this is called.
|
||||
func SetKeepaliveParams(p keepalive.ClientParameters) {
|
||||
kaMu.Lock()
|
||||
kaParams = &p
|
||||
kaMu.Unlock()
|
||||
}
|
||||
|
||||
func getKeepaliveParams() (keepalive.ClientParameters, bool) {
|
||||
kaMu.RLock()
|
||||
p := kaParams
|
||||
kaMu.RUnlock()
|
||||
if p == nil {
|
||||
return keepalive.ClientParameters{}, false
|
||||
}
|
||||
return *p, true
|
||||
}
|
||||
Loaded 100 of 270 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user