mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15114138a8 | ||
|
|
2716090410 | ||
|
|
a805161245 | ||
|
|
bdf4a6ed0d | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
628757250e | ||
|
|
25d242230b | ||
|
|
b4345226c1 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b |
No files matched your search
@@ -42,4 +42,3 @@ 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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,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")
|
||||
@@ -45,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)",
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -798,9 +798,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)
|
||||
@@ -1150,9 +1148,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()
|
||||
|
||||
@@ -1303,9 +1301,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()
|
||||
|
||||
@@ -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>
|
||||
@@ -1409,24 +1416,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) {
|
||||
@@ -1437,34 +1426,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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -131,6 +132,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"
|
||||
)
|
||||
@@ -163,6 +164,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
),
|
||||
)
|
||||
|
||||
form = form.WithTheme(theme.HuhTheme())
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+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
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(transportCreds),
|
||||
grpc.WithPerRPCCredentials(callCreds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
|
||||
if kp, ok := getKeepaliveParams(); ok {
|
||||
options = append(options, grpc.WithKeepaliveParams(kp))
|
||||
}
|
||||
|
||||
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Preserve the legacy grpc.DialContext + grpc.WithBlock behavior: block
|
||||
// until the channel becomes READY or the timeout expires.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
for {
|
||||
state := connection.GetState()
|
||||
if state == connectivity.Idle {
|
||||
connection.Connect()
|
||||
}
|
||||
if state == connectivity.Ready {
|
||||
break
|
||||
}
|
||||
if !connection.WaitForStateChange(ctx, state) {
|
||||
_ = connection.Close()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return rpcpb.NewSliverRPCClient(connection), connection, nil
|
||||
}
|
||||
|
||||
@@ -120,7 +142,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse certificate: " + err.Error())
|
||||
log.Printf("Failed to parse certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
panic("no root certificate")
|
||||
}
|
||||
if _, err := cert.Verify(options); err != nil {
|
||||
log.Printf("Failed to verify certificate: " + err.Error())
|
||||
log.Printf("Failed to verify certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
normal = "\033[0m"
|
||||
bold = "\033[1m"
|
||||
|
||||
defaultVersion = "devel"
|
||||
defaultReleasesURL = "https://api.github.com/repos/BishopFox/sliver/releases"
|
||||
)
|
||||
|
||||
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
"level": 4,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -29,6 +35,10 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
|
||||
- `grpc_unary_payloads` - Log gRPC unary payloads
|
||||
- `grpc_stream_payloads` - Log gRPC streaming payloads
|
||||
- `grpc` - An object containing options for the server gRPC stack
|
||||
- `keepalive` - gRPC keepalive enforcement configuration
|
||||
- `min_time_seconds` - Minimum amount of time (in seconds) between client pings before the server sends a GOAWAY with `too_many_pings`
|
||||
- `permit_without_stream` - Allow client pings when there are no active streams
|
||||
|
||||
## Database Configuration
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ $ cat ~/.sliver/configs/server.json
|
||||
"level": 5,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
### Console Themes
|
||||
|
||||
Sliver’s interactive consoles (both `sliver-client` and the server-only console) use a theme file to drive all lipgloss styling.
|
||||
|
||||
- Theme path: `~/.sliver-client/theme.yaml`
|
||||
- Settings path: `~/.sliver-client/tui-settings.yaml`
|
||||
|
||||
If `theme.yaml` is missing, Sliver will create it with the default palette.
|
||||
|
||||
#### Palette Structure
|
||||
|
||||
`theme.yaml` defines six palettes:
|
||||
|
||||
- `primary`
|
||||
- `secondary`
|
||||
- `default`
|
||||
- `success`
|
||||
- `warning`
|
||||
- `danger`
|
||||
|
||||
Each palette has a base `default` color and a set of modifier colors (`50..900`):
|
||||
|
||||
```yaml
|
||||
primary:
|
||||
default: "#006FEE"
|
||||
mods:
|
||||
50: "#001731"
|
||||
100: "#002e62"
|
||||
200: "#004493"
|
||||
300: "#005bc4"
|
||||
400: "#006FEE"
|
||||
500: "#338ef7"
|
||||
600: "#66aaf9"
|
||||
700: "#99c7fb"
|
||||
800: "#cce3fd"
|
||||
900: "#e6f1fe"
|
||||
```
|
||||
|
||||
### Prompt Settings
|
||||
|
||||
The console prompt is configurable via `tui-settings.yaml`:
|
||||
|
||||
```yaml
|
||||
prompt: "host"
|
||||
prompt_template: |
|
||||
{{- 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 -}}
|
||||
```
|
||||
|
||||
#### `prompt` Modes
|
||||
|
||||
- `operator-host`
|
||||
- `sliver-client`: `[operator@host] sliver >`
|
||||
- server console: `[server] sliver >`
|
||||
- `host`
|
||||
- `sliver-client`: `[host] sliver >`
|
||||
- server console: `[server] sliver >`
|
||||
- `basic`
|
||||
- both: `sliver >`
|
||||
- `custom`
|
||||
- the entire prompt text is rendered from `prompt_template` (Go `text/template`)
|
||||
- if rendering fails, Sliver falls back to a minimal prompt: ` > `
|
||||
|
||||
### Custom Prompt Templates
|
||||
|
||||
When `prompt: custom`, Sliver evaluates `prompt_template` as a Go `text/template` every time the prompt is displayed.
|
||||
|
||||
#### Template Variables
|
||||
|
||||
Top-level values:
|
||||
|
||||
- `.IsServer` (bool)
|
||||
- `.Connected` (bool)
|
||||
- `.Operator` (string)
|
||||
- `.Host` (string)
|
||||
- `.Port` (int)
|
||||
- `.HostPort` (string, `host:port` when available)
|
||||
- `.Now` (time)
|
||||
|
||||
Active target:
|
||||
|
||||
- `.Target.SessionName` (string)
|
||||
- `.Target.BeaconName` (string)
|
||||
- `.Target.Suffix` (string)
|
||||
- pre-rendered themed suffix matching the default prompt, like ` (my-session)` when a session or beacon is selected
|
||||
|
||||
Theme colors:
|
||||
|
||||
- `.Colors.Primary.Default`
|
||||
- `.Colors.Primary.Mods` (map; use `index`, e.g. `index .Colors.Primary.Mods 500`)
|
||||
- same structure for `.Colors.Secondary`, `.Colors.Default`, `.Colors.Success`, `.Colors.Warning`, `.Colors.Danger`
|
||||
|
||||
Theme styles (lipgloss styles already wired to `theme.yaml`):
|
||||
|
||||
- `.Styles.Bold`, `.Styles.Underline`
|
||||
- `.Styles.Primary`, `.Styles.Secondary`, `.Styles.Default`, `.Styles.Success`, `.Styles.Warning`, `.Styles.Danger`
|
||||
- `.Styles.BoldPrimary`, `.Styles.BoldSecondary`, `.Styles.BoldDefault`, `.Styles.BoldSuccess`, `.Styles.BoldWarning`, `.Styles.BoldDanger`
|
||||
- legacy names: `.Styles.Red`, `.Styles.Green`, `.Styles.Orange`, `.Styles.Blue`, `.Styles.Purple`, `.Styles.Cyan`, `.Styles.Gray`, `.Styles.Black`
|
||||
|
||||
#### Examples
|
||||
|
||||
Show user and host (client), show server tag (server console):
|
||||
|
||||
```gotemplate
|
||||
{{- if .IsServer -}}
|
||||
{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
|
||||
{{- else -}}
|
||||
{{- if and .Operator .Host -}}
|
||||
{{ .Styles.BoldPrimary.Render (printf "[%s@%s]" .Operator .Host) }}
|
||||
{{- else if .Host -}}
|
||||
{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }}
|
||||
{{- end -}}
|
||||
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
Basic prompt everywhere:
|
||||
|
||||
```gotemplate
|
||||
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
|
||||
```
|
||||
|
||||
Use a lighter primary modifier for the host tag:
|
||||
|
||||
```gotemplate
|
||||
{{- $c := index .Colors.Primary.Mods 500 -}}
|
||||
{{- if .Host -}}{{ (.Styles.Bold.Foreground $c).Render (printf "[%s]" .Host) }} {{- end -}}
|
||||
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
|
||||
```
|
||||
@@ -202,6 +202,15 @@ else
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (dns)
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'TestDNS_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
|
||||
# server / configs
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTrackedExecuteChildren = 256
|
||||
)
|
||||
|
||||
type executeChildEntry struct {
|
||||
PID int32
|
||||
Path string
|
||||
Args []string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StartTime int64
|
||||
|
||||
Exited bool
|
||||
ExitCode int32
|
||||
ExitTime int64
|
||||
Error string
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
var (
|
||||
executeChildrenMu sync.RWMutex
|
||||
trackedExecuteChildren = map[int32]*executeChildEntry{}
|
||||
)
|
||||
|
||||
func trimTrackedExecuteChildrenLocked() {
|
||||
if len(trackedExecuteChildren) <= maxTrackedExecuteChildren {
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer trimming completed entries first, oldest exit time first.
|
||||
exited := make([]*executeChildEntry, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
if entry.Exited {
|
||||
exited = append(exited, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(exited, func(i, j int) bool {
|
||||
return exited[i].ExitTime < exited[j].ExitTime
|
||||
})
|
||||
|
||||
for len(trackedExecuteChildren) > maxTrackedExecuteChildren && 0 < len(exited) {
|
||||
delete(trackedExecuteChildren, exited[0].PID)
|
||||
exited = exited[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func startExecuteChild(cmd *exec.Cmd, track bool, path string, args []string, stdoutPath string, stderrPath string) (int32, error) {
|
||||
var (
|
||||
stdoutFile *os.File
|
||||
stderrFile *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
startTime := time.Now().Unix()
|
||||
|
||||
if stdoutPath != "" {
|
||||
stdoutFile, err = os.Create(stdoutPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stdout = stdoutFile
|
||||
}
|
||||
if stderrPath != "" {
|
||||
stderrFile, err = os.Create(stderrPath)
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stderr = stderrFile
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pid int32
|
||||
if cmd.Process != nil {
|
||||
pid = int32(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
entry := &executeChildEntry{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
Args: append([]string(nil), args...),
|
||||
Stdout: stdoutPath,
|
||||
Stderr: stderrPath,
|
||||
StartTime: startTime,
|
||||
cmd: cmd,
|
||||
}
|
||||
executeChildrenMu.Lock()
|
||||
trackedExecuteChildren[pid] = entry
|
||||
trimTrackedExecuteChildrenLocked()
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
go func(pid int32, cmd *exec.Cmd, stdoutFile *os.File, stderrFile *os.File, track bool) {
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
exitCode := int32(0)
|
||||
waitErrStr := ""
|
||||
if waitErr != nil {
|
||||
if exitErr, ok := waitErr.(*exec.ExitError); ok {
|
||||
exitCode = int32(exitErr.ExitCode())
|
||||
} else {
|
||||
exitCode = -1
|
||||
waitErrStr = waitErr.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
executeChildrenMu.Lock()
|
||||
entry := trackedExecuteChildren[pid]
|
||||
if entry != nil {
|
||||
entry.Exited = true
|
||||
entry.ExitCode = exitCode
|
||||
entry.ExitTime = time.Now().Unix()
|
||||
entry.Error = waitErrStr
|
||||
entry.cmd = nil
|
||||
}
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
}(pid, cmd, stdoutFile, stderrFile, track)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func snapshotExecuteChildren() []*sliverpb.ExecuteChild {
|
||||
executeChildrenMu.RLock()
|
||||
children := make([]*sliverpb.ExecuteChild, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
children = append(children, &sliverpb.ExecuteChild{
|
||||
Pid: entry.PID,
|
||||
Path: entry.Path,
|
||||
Args: append([]string(nil), entry.Args...),
|
||||
StartTime: entry.StartTime,
|
||||
Exited: entry.Exited,
|
||||
ExitCode: entry.ExitCode,
|
||||
ExitTime: entry.ExitTime,
|
||||
Stdout: entry.Stdout,
|
||||
Stderr: entry.Stderr,
|
||||
Error: entry.Error,
|
||||
})
|
||||
}
|
||||
executeChildrenMu.RUnlock()
|
||||
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
func executeChildrenHandler(data []byte, resp RPCResponse) {
|
||||
req := &sliverpb.ExecuteChildrenReq{}
|
||||
err := proto.Unmarshal(data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
execChildren := &sliverpb.ExecuteChildren{
|
||||
Children: snapshotExecuteChildren(),
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
data, err = proto.Marshal(execChildren)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
}()
|
||||
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
|
||||
@@ -33,25 +33,26 @@ import (
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
@@ -34,23 +34,24 @@ import (
|
||||
|
||||
var (
|
||||
genericHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||
|
||||
@@ -45,24 +45,25 @@ import (
|
||||
|
||||
var (
|
||||
linuxHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||
|
||||
|
||||
@@ -96,21 +96,22 @@ var (
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
@@ -386,7 +387,7 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.Bytes()
|
||||
execResp.Stdout = stdOutBuff.Bytes()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
|
||||
@@ -37,6 +37,8 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
||||
// {{end}}
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
return errors.New("[mtls] connect returned nil conn")
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
_ = conn.Close()
|
||||
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Recv: func() (*pb.Envelope, error) {
|
||||
if muxSession == nil {
|
||||
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return nil, errors.New("[mtls] accept returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.ReadEnvelope(stream)
|
||||
},
|
||||
Send: func(envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if muxSession == nil {
|
||||
return errors.New("[mtls] send called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return errors.New("[mtls] open returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.WriteEnvelope(stream, envelope)
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
}
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
NewGenericResolver(address, port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||
// {{end}}
|
||||
|
||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
// Generate a random session ID locally. The server will only allocate state
|
||||
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||
if s.dnsSessionID == 0 {
|
||||
sid, err := s.randomDNSSessionID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsSessionID = sid
|
||||
}
|
||||
|
||||
// Key agreement with server
|
||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
resolver, _ := s.randomResolver()
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
ID: s.nextMsgID(),
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg send failure %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
var respData []byte
|
||||
var initErr error
|
||||
for _, resolver := range s.resolvers {
|
||||
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if initErr != nil {
|
||||
continue
|
||||
}
|
||||
if len(respData) < 1 {
|
||||
initErr = ErrEmptyResponse
|
||||
continue
|
||||
}
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
continue
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
initErr = errInvalidDNSSessionID
|
||||
continue
|
||||
}
|
||||
initErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
if initErr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg decryption failure %v", err)
|
||||
log.Printf("[dns] init msg send failure %v", initErr)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg dns session id mismatch")
|
||||
// {{end}}
|
||||
return err
|
||||
return initErr
|
||||
}
|
||||
|
||||
// Good to go!
|
||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
log.Printf("[dns] key exchange was successful!")
|
||||
// {{end}}
|
||||
|
||||
// Now that we have a server-side session, benchmark/fingerprint resolvers and
|
||||
// drop ones that error.
|
||||
s.fingerprintResolvers()
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] starting worker(s) ...")
|
||||
// {{end}}
|
||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
||||
otpMsg, err := s.otpMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
||||
// {{end}}
|
||||
|
||||
var a []byte
|
||||
for _, resolver := range s.resolvers {
|
||||
a, _, err = resolver.A(otpDomain)
|
||||
if err == nil {
|
||||
break
|
||||
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||
// sessionID is 24-bit and must be non-zero.
|
||||
randBuf := make([]byte, 4)
|
||||
for {
|
||||
_, err := rand.Read(randBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||
if sid != 0 {
|
||||
return sid, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err // All resolvers failed
|
||||
}
|
||||
if len(a) < 1 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
s.dnsSessionID = binary.LittleEndian.Uint32(a) & sessionIDBitMask
|
||||
if s.dnsSessionID == 0 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] dns session id: %d", s.dnsSessionID)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) loadResolvConf() error {
|
||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func splitResolverAddress(server string, defaultPort string) (string, string) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return "", defaultPort
|
||||
}
|
||||
|
||||
// Allow tests/operators to specify an explicit port, e.g. "127.0.0.1:5353"
|
||||
// or "[::1]:5353". Raw IPv6 addresses in resolv.conf typically do not include
|
||||
// a port and will fail SplitHostPort, in which case we fall back to defaultPort.
|
||||
if host, port, err := net.SplitHostPort(server); err == nil {
|
||||
return host, port
|
||||
}
|
||||
if strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]") {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(server, "["), "]"), defaultPort
|
||||
}
|
||||
return server, defaultPort
|
||||
}
|
||||
|
||||
// Joins subdata to the parent domain, you must have already done the math to
|
||||
// ensure the subdata can fit in the domain
|
||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: uint32(0), // Take advantage of the variable length encoding
|
||||
}
|
||||
data, err := proto.Marshal(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg, _ := s.base32.Encode(data)
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
@@ -230,28 +230,17 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTPMsgRoundTrip(t *testing.T) {
|
||||
func TestRandomDNSSessionID(t *testing.T) {
|
||||
client := NewDNSClient(parent1, opts)
|
||||
encoded, err := client.otpMsg()
|
||||
sid, err := client.randomDNSSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
||||
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||
}
|
||||
if !isBase32String(encoded) {
|
||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
||||
if sid == 0 {
|
||||
t.Fatal("expected non-zero dns session id")
|
||||
}
|
||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg decode error: %s", err)
|
||||
}
|
||||
msg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, msg); err != nil {
|
||||
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
|
||||
}
|
||||
if msg.Type != dnspb.DNSMessageType_TOTP {
|
||||
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
|
||||
}
|
||||
if msg.ID != 0 {
|
||||
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
|
||||
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
retries = 1
|
||||
}
|
||||
return &GenericResolver{
|
||||
address: address + ":" + port,
|
||||
address: net.JoinHostPort(address, port),
|
||||
retries: retries,
|
||||
retryWait: retryWait,
|
||||
resolver: &dns.Client{
|
||||
|
||||
@@ -21,7 +21,6 @@ package mtls
|
||||
// {{if .Config.IncludeMTLS}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
||||
}
|
||||
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeAll(w io.Writer, p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if isNilInterface(w) {
|
||||
return errors.New("[mtls] nil writer")
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||
}
|
||||
|
||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||
}
|
||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
||||
return werr
|
||||
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||
}
|
||||
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
||||
var dataLengthBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data length: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data length: %w", werr)
|
||||
}
|
||||
if _, werr := w.Write(data); werr != nil {
|
||||
if werr := writeAll(w, data); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package mtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func TestWriteEnvelope_NilEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteEnvelope(&buf, nil); err == nil {
|
||||
t.Fatalf("expected error for nil envelope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := WriteEnvelope(nil, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilTLSConnWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var conn *tls.Conn
|
||||
if err := WriteEnvelope(conn, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil *tls.Conn writer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package transports
|
||||
|
||||
import "reflect"
|
||||
|
||||
// isNilInterface returns true if v is nil, or if v is an interface value holding a nil pointer.
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -45,8 +47,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "mtls connection returned nil conn"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
conn.Close()
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "[mtls] failed to create yamux session (nil)"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
connection.IsOpen = true
|
||||
|
||||
go func() {
|
||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WritePing(stream); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case streamSem <- struct{}{}:
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package yamuxtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func TestYamuxConcurrentStreams(t *testing.T) {
|
||||
serverConn, clientConn := net.Pipe()
|
||||
serverSession, err := yamux.Server(serverConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("server session: %v", err)
|
||||
}
|
||||
defer serverSession.Close()
|
||||
|
||||
clientSession, err := yamux.Client(clientConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("client session: %v", err)
|
||||
}
|
||||
defer clientSession.Close()
|
||||
|
||||
const streams = 16
|
||||
payloads := make([][]byte, streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
payloads[i] = []byte(fmt.Sprintf("stream=%d payload=%s", i, bytes.Repeat([]byte{byte('a' + i)}, 4096)))
|
||||
}
|
||||
|
||||
var openWg sync.WaitGroup
|
||||
openWg.Add(streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer openWg.Done()
|
||||
stream, err := clientSession.Open()
|
||||
if err != nil {
|
||||
t.Errorf("open stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
_, err = stream.Write(payloads[i])
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Errorf("write stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
openWg.Wait()
|
||||
|
||||
wantSet := map[string]struct{}{}
|
||||
for _, p := range payloads {
|
||||
wantSet[string(p)] = struct{}{}
|
||||
}
|
||||
|
||||
gotSet := map[string]struct{}{}
|
||||
for i := 0; i < streams; i++ {
|
||||
stream, err := serverSession.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("accept stream %d: %v", i, err)
|
||||
}
|
||||
b, err := io.ReadAll(stream)
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("read stream %d: %v", i, err)
|
||||
}
|
||||
gotSet[string(b)] = struct{}{}
|
||||
}
|
||||
|
||||
if len(gotSet) != len(wantSet) {
|
||||
t.Fatalf("unexpected payload count: got=%d want=%d", len(gotSet), len(wantSet))
|
||||
}
|
||||
for want := range wantSet {
|
||||
if _, ok := gotSet[want]; !ok {
|
||||
t.Fatalf("missing payload: %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+650
-460
File diff suppressed because it is too large.
Load diff
Loaded 100 of 128 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user