Compare commits

...
44 Commits
Author SHA1 Message Date
rkervella 63c316e4d7 Set full file path 2021-12-30 09:48:39 -08:00
moloch-- cf8e6ace2e Basic implementation of bundles 2021-12-30 10:47:10 -06:00
moloch-- 131061a015 Implemented update cmd 2021-12-30 10:09:02 -06:00
moloch-- ed18705ccf Use fork of grumble so we can remove cmds 2021-12-30 09:51:46 -06:00
moloch-- 88be0e0846 Added search cmd 2021-12-29 17:46:42 -06:00
moloch-- edd330a4e1 Make coff-loader removable 2021-12-29 17:11:38 -06:00
moloch-- 26c4d04d8d Use cmd name as key to pkgCache 2021-12-29 17:09:37 -06:00
moloch-- 283f605351 Fix variable shadowing, improved ux 2021-12-29 08:39:45 -06:00
moloch-- fd82c4b171 Added an update cmd 2021-12-28 16:24:40 -06:00
rkervella 3d0d61685c Avoid adding empty dependency 2021-12-28 09:04:08 -08:00
moloch-- 177358040f Skip installation of dep if it already exists 2021-12-28 08:44:28 -06:00
moloch-- e4db7d3323 Added a max depth 2021-12-28 08:39:18 -06:00
moloch-- 7b42983e4f Avoid n-depth circular deps 2021-12-28 08:37:24 -06:00
moloch-- 441f97ad6f Added install alias and ext dep resolution 2021-12-28 08:28:12 -06:00
moloch-- efea316519 wip: dep resolver 2021-12-27 22:27:05 -06:00
moloch-- f391f72b04 update extensions readme 2021-12-27 21:26:06 -06:00
moloch-- 2ebaf3115c update extensions readme 2021-12-27 21:25:44 -06:00
moloch-- 630adc0e48 update extensions readme 2021-12-27 21:24:39 -06:00
moloch-- 981aa8b80f add alias rm 2021-12-27 20:33:19 -06:00
moloch-- 024f70edc5 Prevent removal of coff-loader ext 2021-12-27 19:38:32 -06:00
moloch-- bf9b3f4aa6 Eager load installed cmds 2021-12-27 19:26:14 -06:00
moloch-- 5fc94d24bd Added an extensions rm cmd 2021-12-27 18:13:01 -06:00
moloch-- 5394f77f4a Implemented table 2021-12-27 17:29:06 -06:00
moloch-- b41ac3a229 Basic pkg info working 2021-12-27 17:09:13 -06:00
moloch-- 5c3fd8cb2b wip: armory cmd 2021-12-27 09:59:56 -06:00
moloch-- adbe7c7def Consisten ux 2021-12-26 21:43:34 -06:00
moloch-- bfcbb0bba6 Small fixes 2021-12-26 18:18:56 -06:00
moloch-- 4495c36853 added unit tests for alias parser 2021-12-26 17:27:12 -06:00
moloch-- f8d2f30344 added unit tests for ext manifest parsing 2021-12-26 16:08:00 -06:00
moloch-- 55752d1109 added os specific path handling to util 2021-12-26 12:15:29 -06:00
moloch-- 48f7560655 Implemented alias install cmd 2021-12-26 10:05:03 -06:00
moloch-- 92e0f87d44 Implemented ext install cmd 2021-12-24 19:36:21 -06:00
moloch-- 13498da60c Implemented more ux features for extensions 2021-12-24 14:25:30 -06:00
moloch-- 9c878527a5 Small refactor of exts layout 2021-12-24 12:47:56 -06:00
moloch-- f81419614d Added local file completer, and more generic client asset unpacker 2021-12-24 11:46:21 -06:00
moloch-- 368442e094 Small refactor of macro->alias and exts 2021-12-24 10:09:49 -06:00
moloch-- 670c4b7f9a Improved ux 2021-12-24 09:19:37 -06:00
moloch-- 77693c6be1 socks and wg-socks use same subcommands 2021-12-23 20:51:58 -06:00
moloch-- 35bd3fd44a Converted website tables 2021-12-23 20:45:20 -06:00
moloch-- a916f8b38b Converted wg tables 2021-12-23 20:39:52 -06:00
moloch-- fa2645e186 Converted jobs tables 2021-12-23 20:27:23 -06:00
moloch-- 597950fa0a Converted more tables 2021-12-23 20:18:37 -06:00
moloch-- 2acced66f1 Minor cleanup 2021-12-23 17:43:16 -06:00
rkervella 39a6e13ae4 Remove last CGO artifacts for shellcode generation 2021-12-23 10:38:19 -08:00
87 changed files with 4239 additions and 2054 deletions

No files matched your search

+18 -13
View File
@@ -13,16 +13,21 @@ GO_VERSION = $(shell $(GO) version)
VERSION ?= $(shell git describe --abbrev=0)
COMPILED_AT = $(shell date +%s)
RELEASES_URL = https://api.github.com/repos/BishopFox/sliver/releases
PKG = github.com/bishopfox/sliver/client/version
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
ARMORY_PUB_KEY = RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL = https://api.github.com/repos/sliverarmory/armory/releases
VERSION_PKG = github.com/bishopfox/sliver/client/version
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
GIT_COMMIT = $(shell git rev-parse HEAD)
LDFLAGS = -ldflags "-s -w \
-X $(PKG).Version=$(VERSION) \
-X \"$(PKG).GoVersion=$(GO_VERSION)\" \
-X $(PKG).CompiledAt=$(COMPILED_AT) \
-X $(PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(PKG).GitCommit=$(GIT_COMMIT) \
-X $(PKG).GitDirty=$(GIT_DIRTY)"
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
#
@@ -70,12 +75,12 @@ ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \
-X $(PKG).Version=$(VERSION) \
-X \"$(PKG).GoVersion=$(GO_VERSION)\" \
-X $(PKG).CompiledAt=$(COMPILED_AT) \
-X $(PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(PKG).GitCommit=$(GIT_COMMIT) \
-X $(PKG).GitDirty=$(GIT_DIRTY)"
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY)"
endif
#
+1 -9
View File
@@ -1,9 +1 @@
fs/*.zip
fs/extensions/darwin/amd64/
fs/extensions/darwin/arm64/
fs/extensions/linux/amd64/
fs/extensions/windows/amd64/
fs/extensions/darwin/386/
fs/extensions/darwin/386/
fs/extensions/linux/386/
fs/extensions/windows/386/
fs/**
+65
View File
@@ -0,0 +1,65 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"log"
"os"
"path/filepath"
)
const (
AliasesDirName = "aliases"
)
// GetAliasesDir - Returns the path to the config dir
func GetAliasesDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := filepath.Join(rootDir, AliasesDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetInstalledAliasManifests - Returns a list of installed alias manifests
func GetInstalledAliasManifests() []string {
aliasDir := GetAliasesDir()
aliasDirContent, err := ioutil.ReadDir(aliasDir)
if err != nil {
log.Printf("error loading aliases: %s", err)
return []string{}
}
manifests := []string{}
for _, fi := range aliasDirContent {
if fi.IsDir() {
manifestPath := filepath.Join(aliasDir, fi.Name(), "alias.json")
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
log.Printf("no manifest in %s, skipping ...", manifestPath)
continue
}
manifests = append(manifests, manifestPath)
}
}
return manifests
}
+63
View File
@@ -0,0 +1,63 @@
package assets
/*
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 (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
)
const (
armoryConfigFileName = "armories.json"
)
var (
DefaultArmoryPublicKey string
DefaultArmoryRepoURL string
defaultArmoryConfig = &ArmoryConfig{
PublicKey: DefaultArmoryPublicKey,
RepoURL: DefaultArmoryRepoURL,
}
)
type ArmoryConfig struct {
PublicKey string `json:"public_key"`
RepoURL string `json:"repo_url"`
}
// GetArmoriesConfig - The parsed armory config file
func GetArmoriesConfig() []*ArmoryConfig {
armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
return []*ArmoryConfig{defaultArmoryConfig}
}
data, err := ioutil.ReadFile(armoryConfigPath)
if err != nil {
return []*ArmoryConfig{defaultArmoryConfig}
}
var armoryConfigs []*ArmoryConfig
err = json.Unmarshal(data, &armoryConfigs)
if err != nil {
return []*ArmoryConfig{defaultArmoryConfig}
}
return append(armoryConfigs, defaultArmoryConfig)
}
+40 -62
View File
@@ -21,11 +21,11 @@ package assets
import (
"embed"
"fmt"
"io/fs"
"io/ioutil"
"log"
"os"
"os/user"
"path"
"path/filepath"
"strings"
@@ -33,35 +33,21 @@ import (
)
var (
//go:embed fs/extensions/*
assetsFs embed.FS
//go:embed fs/*
clientAssetsFs embed.FS
)
const (
// SliverClientDirName - Directory storing all of the client configs/logs
SliverClientDirName = ".sliver-client"
// SliverExtensionsDirName - Directory storing the client side extensions
SliverExtensionsDirName = "extensions"
versionFileName = "version"
versionFileName = "version"
)
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
func GetRootAppDir() string {
user, _ := user.Current()
dir := path.Join(user.HomeDir, SliverClientDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
func GetExtensionsDir() string {
user, _ := user.Current()
dir := path.Join(user.HomeDir, SliverClientDirName, SliverExtensionsDirName)
dir := filepath.Join(user.HomeDir, SliverClientDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -73,7 +59,7 @@ func GetExtensionsDir() string {
func assetVersion() string {
appDir := GetRootAppDir()
data, err := ioutil.ReadFile(path.Join(appDir, versionFileName))
data, err := ioutil.ReadFile(filepath.Join(appDir, versionFileName))
if err != nil {
return ""
}
@@ -81,7 +67,7 @@ func assetVersion() string {
}
func saveAssetVersion(appDir string) {
versionFilePath := path.Join(appDir, versionFileName)
versionFilePath := filepath.Join(appDir, versionFileName)
fVer, _ := os.Create(versionFilePath)
defer fVer.Close()
fVer.Write([]byte(ver.GitCommit))
@@ -93,9 +79,14 @@ func Setup(force bool, echo bool) {
localVer := assetVersion()
if force || localVer == "" || localVer != ver.GitCommit {
if echo {
fmt.Printf("Unpacking assets ...\n")
fmt.Printf(`
Sliver Copyright (C) 2021 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
fmt.Printf("\n\nUnpacking assets ...\n")
}
err := setupCoffLoaderExt(appDir)
err := setupDefaultExtensions(appDir)
if err != nil {
fmt.Println(err)
log.Fatal(err)
@@ -107,42 +98,29 @@ func Setup(force bool, echo bool) {
}
}
func setupCoffLoaderExt(appDir string) error {
extDir := GetExtensionsDir()
win32ExtDir := path.Join("windows", "386")
win64ExtDir := path.Join("windows", "amd64")
coffLoader32 := path.Join("fs", SliverExtensionsDirName, win32ExtDir, "COFFLoader.x86.dll")
coffLoader64 := path.Join("fs", SliverExtensionsDirName, win64ExtDir, "COFFLoader.x64.dll")
manifestPath := path.Join("fs", SliverExtensionsDirName, "manifest.json")
loader64, err := assetsFs.ReadFile(coffLoader64)
if err != nil {
return err
}
loader32, err := assetsFs.ReadFile(coffLoader32)
if err != nil {
return err
}
manifest, err := assetsFs.ReadFile(manifestPath)
if err != nil {
return err
}
localWin32ExtDir := path.Join(extDir, win32ExtDir)
err = os.MkdirAll(localWin32ExtDir, 0700)
if err != nil {
return err
}
localWin64ExtDir := path.Join(extDir, win64ExtDir)
err = os.MkdirAll(localWin64ExtDir, 0700)
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(localWin32ExtDir, "COFFLoader.x86.dll"), loader32, 0744)
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(extDir, "manifest.json"), manifest, 0700)
if err != nil {
return err
}
return ioutil.WriteFile(path.Join(localWin64ExtDir, "COFFLoader.x64.dll"), loader64, 0744)
func setupDefaultExtensions(appDir string) error {
localExtDir := GetExtensionsDir()
rootEmbedPath := "fs/extensions"
return fs.WalkDir(clientAssetsFs, rootEmbedPath, func(embedPath string, embedDir fs.DirEntry, err error) error {
if err != nil {
return err
}
localPath := strings.TrimPrefix(embedPath, rootEmbedPath)
if embedDir.IsDir() {
err := os.MkdirAll(filepath.Join(localExtDir, localPath), 0o700)
if err != nil {
return err
}
} else {
data, err := fs.ReadFile(clientAssetsFs, embedPath)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(localExtDir, localPath), data, 0o600)
if err != nil {
return err
}
}
return nil
})
}
+4 -4
View File
@@ -49,7 +49,7 @@ type ClientConfig struct {
// GetConfigDir - Returns the path to the config dir
func GetConfigDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := path.Join(rootDir, ConfigDirName)
dir := filepath.Join(rootDir, ConfigDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -86,11 +86,11 @@ func GetConfigs() map[string]*ClientConfig {
// ReadConfig - Load config into struct
func ReadConfig(confFilePath string) (*ClientConfig, error) {
confFile, err := os.Open(confFilePath)
defer confFile.Close()
if err != nil {
log.Printf("Open failed %v", err)
return nil, err
}
defer confFile.Close()
data, err := ioutil.ReadAll(confFile)
if err != nil {
log.Printf("Read failed %v", err)
@@ -108,11 +108,11 @@ func ReadConfig(confFilePath string) (*ClientConfig, error) {
// SaveConfig - Save a config to disk
func SaveConfig(config *ClientConfig) error {
if config.LHost == "" || config.Operator == "" {
return errors.New("Empty config")
return errors.New("empty config")
}
configDir := GetConfigDir()
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(config.Operator), filepath.Base(config.LHost))
saveTo, _ := filepath.Abs(path.Join(configDir, filename))
saveTo, _ := filepath.Abs(filepath.Join(configDir, filename))
configJSON, _ := json.Marshal(config)
err := ioutil.WriteFile(saveTo, configJSON, 0600)
if err != nil {
+66
View File
@@ -0,0 +1,66 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"log"
"os"
"path/filepath"
)
const (
// ExtensionsDirName - Directory storing the client side extensions
ExtensionsDirName = "extensions"
)
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
func GetExtensionsDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := filepath.Join(rootDir, ExtensionsDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetInstalledExtensionManifests - Returns a list of installed extension manifests
func GetInstalledExtensionManifests() []string {
extDir := GetExtensionsDir()
extDirContent, err := ioutil.ReadDir(extDir)
if err != nil {
log.Printf("error loading aliases: %s", err)
return []string{}
}
manifests := []string{}
for _, fi := range extDirContent {
if fi.IsDir() {
manifestPath := filepath.Join(extDir, fi.Name(), "extension.json")
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
log.Printf("no manifest in %s, skipping ...", manifestPath)
continue
}
manifests = append(manifests, manifestPath)
}
}
return manifests
}
-16
View File
@@ -1,16 +0,0 @@
[
{
"name": "coff-loader",
"help": "Load and execute Beacon Object Files (BOFs) in memory.",
"entrypoint": "LoadAndRun",
"extFiles": [
{
"os": "windows",
"files": {
"x64": "COFFLoader.x64.dll",
"x86": "COFFLoader.x86.dll"
}
}
]
}
]
@@ -1,4 +1,11 @@
Macros
Alias
===========
Wraps external DLLs/tool in Sliver console commands.
### Format
* `alias-dir/`
* * `manifest.json` -
+75
View File
@@ -0,0 +1,75 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"strings"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// AliasesCmd - The alias command
func AliasesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if 0 < len(loadedAliases) {
PrintAliases(con)
} else {
con.PrintInfof("No aliases loaded\n")
}
}
// PrintAliases - Print a list of loaded aliases
func PrintAliases(con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Command Name",
".NET Assembly",
"Reflective",
"Help",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for _, alias := range loadedAliases {
tw.AppendRow(table.Row{
alias.Manifest.Name,
alias.Manifest.CommandName,
alias.Manifest.IsAssembly,
alias.Manifest.IsReflective,
alias.Manifest.Help,
})
}
con.Println(tw.Render())
}
// AliasCommandNameCompleter - Completer for installed extensions command names
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
results := []string{}
for name := range loadedAliases {
if strings.HasPrefix(name, prefix) {
results = append(results, name)
}
}
return results
}
+90
View File
@@ -0,0 +1,90 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 "testing"
const (
sample1 = `{
"name": "test1",
"command_name": "test1",
"version": "1.0.0",
"repo_url": "https://example.com/",
"help": "some help",
"entrypoint": "foo",
"allow_args": true,
"default_args": "bar",
"is_reflective": true,
"is_assembly": true,
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
)
func TestParseAliasManifest(t *testing.T) {
alias1, err := ParseAliasManifest([]byte(sample1))
if err != nil {
t.Errorf("Error parsing alias manifest: %s", err)
}
if alias1.Name != "test1" {
t.Errorf("Expected name to be 'test1', got '%s'", alias1.Name)
}
if alias1.CommandName != "test1" {
t.Errorf("Expected command_name to be 'test1', got '%s'", alias1.CommandName)
}
if alias1.Version != "1.0.0" {
t.Errorf("Expected version to be '1.0.0', got '%s'", alias1.Version)
}
if alias1.RepoURL != "https://example.com/" {
t.Errorf("Expected repo_url to be 'https://example.com/', got '%s'", alias1.RepoURL)
}
if alias1.Help != "some help" {
t.Errorf("Expected help to be 'some help', got '%s'", alias1.Help)
}
if alias1.Entrypoint != "foo" {
t.Errorf("Expected entrypoint to be 'foo', got '%s'", alias1.Entrypoint)
}
if !alias1.AllowArgs {
t.Errorf("Expected allow_args to be true, got false")
}
if alias1.DefaultArgs != "bar" {
t.Errorf("Expected default_args to be 'bar', got '%s'", alias1.DefaultArgs)
}
if !alias1.IsReflective {
t.Errorf("Expected is_reflective to be true, got false")
}
if !alias1.IsAssembly {
t.Errorf("Expected is_assembly to be true, got false")
}
}
+174
View File
@@ -0,0 +1,174 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// AliasesInstallCmd - Install an alias
func AliasesInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
aliasLocalPath := ctx.Args.String("path")
fi, err := os.Stat(aliasLocalPath)
if os.IsNotExist(err) {
con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
return
}
if !fi.IsDir() {
InstallFromFile(aliasLocalPath, false, con)
} else {
installFromDir(aliasLocalPath, con)
}
}
// Install an extension from a directory
func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := ioutil.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
return
}
manifest, err := ParseAliasManifest(manifestData)
if err != nil {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.Name)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nError creating alias directory: %s\n", err)
return
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return
}
for _, cmdFile := range manifest.Files {
if cmdFile.Path != "" {
src := filepath.Join(aliasLocalPath, util.ResolvePath(cmdFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(cmdFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
forceRemoveAll(installPath)
return
}
}
}
con.Printf("done!\n")
}
// Install an extension from a .tar.gz file
func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
return nil
}
manifest, err := ParseAliasManifest(manifestData)
if err != nil {
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
return nil
}
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil
}
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nFailed to create alias directory: %s\n", err)
return nil
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return nil
}
for _, aliasFile := range manifest.Files {
if aliasFile.Path != "" {
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
if err != nil {
con.PrintErrorf("\nFailed to install file: %s\n", err)
forceRemoveAll(installPath)
return nil
}
}
}
con.Printf("done!\n")
return &installPath
}
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(aliasGzFilePath, artifactPath)
if err != nil {
return err
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
os.MkdirAll(artifactDir, 0o700)
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
+364
View File
@@ -0,0 +1,364 @@
package alias
/*
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"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
ManifestFileName = "alias.json"
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
linuxDefaultHostProc = "/bin/bash"
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
)
var (
// alias name -> manifest/command
loadedAliases = map[string]*loadedAlias{}
defaultHostProc = map[string]string{
"windows": windowsDefaultHostProc,
"linux": windowsDefaultHostProc,
"darwin": macosDefaultHostProc,
}
)
// Ties the manifest struct to the command struct
type loadedAlias struct {
Manifest *AliasManifest
Command *grumble.Command
}
type AliasFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type AliasManifest struct {
Name string `json:"name"`
Version string `json:"version"`
CommandName string `json:"command_name"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
LongHelp string `json:"long_help"`
Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"`
Files []*AliasFile `json:"files"`
IsReflective bool `json:"is_reflective"`
IsAssembly bool `json:"is_assembly"`
RootPath string `json:"-"`
}
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
proc, ok := defaultHostProc[targetOS]
if !ok {
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
}
return
}
func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
var filePath string
var err error
for _, ef := range a.Files {
if targetOS == ef.OS && targetArch == ef.Arch {
filePath = ef.Path
break
}
}
if filePath == "" {
err = fmt.Errorf("no alias file found for %s/%s", targetOS, targetArch)
}
return filePath, err
}
// AliasesLoadCmd - Locally load a alias into the Sliver shell.
func AliasesLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
alias, err := LoadAlias(dirPath, con)
if err != nil {
con.PrintErrorf("Failed to load alias: %s\n", err)
} else {
con.PrintInfof("%s alias has been loaded\n", alias.Name)
}
}
// LoadAlias - Load an alias into the Sliver shell from a given directory
func LoadAlias(dirPath string, con *console.SliverConsoleClient) (*AliasManifest, error) {
// retrieve alias manifest
var err error
dirPath, err = filepath.Abs(dirPath)
if err != nil {
return nil, err
}
// parse it
manifestPath := filepath.Join(dirPath, ManifestFileName)
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
alias, err := ParseAliasManifest(data)
if err != nil {
return nil, err
}
alias.RootPath = filepath.Dir(manifestPath)
// for each alias command, add a new app command
// do not add if the command already exists
if cmdExists(alias.CommandName, con.App) {
return nil, fmt.Errorf("'%s' command already exists", alias.CommandName)
}
helpMsg := fmt.Sprintf("[%s] %s", alias.Name, alias.Help)
addAliasCmd := &grumble.Command{
Name: alias.Name,
Help: helpMsg,
LongHelp: help.FormatHelpTmpl(alias.LongHelp),
Run: func(extCtx *grumble.Context) error {
con.Println()
runAliasCommand(extCtx, con)
con.Println()
return nil
},
Flags: func(f *grumble.Flags) {
if alias.IsAssembly {
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
}
f.String("p", "process", "", "Path to process to host the shared object")
f.Bool("s", "save", false, "Save output to disk")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.StringList("arguments", "arguments", grumble.Default([]string{}))
},
HelpGroup: consts.AliasHelpGroup,
}
con.App.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd
// either by value or by ref fucks things up
loadedAliases[alias.Name] = &loadedAlias{
Manifest: alias,
Command: addAliasCmd,
}
return alias, nil
}
func ParseAliasManifest(data []byte) (*AliasManifest, error) {
// parse it
alias := &AliasManifest{}
err := json.Unmarshal(data, alias)
if err != nil {
return nil, err
}
if alias.Name == "" {
return nil, fmt.Errorf("missing alias name in manifest")
}
if alias.CommandName == "" {
return nil, fmt.Errorf("missing command.name in alias manifest")
}
if alias.Help == "" {
return nil, fmt.Errorf("missing command.help in alias manifest")
}
for _, aliasFile := range alias.Files {
if aliasFile.OS == "" {
return nil, fmt.Errorf("missing command.files.os in alias manifest")
}
aliasFile.OS = strings.ToLower(aliasFile.OS)
if aliasFile.Arch == "" {
return nil, fmt.Errorf("missing command.files.arch in alias manifest")
}
aliasFile.Arch = strings.ToLower(aliasFile.Arch)
aliasFile.Path = util.ResolvePath(aliasFile.Path)
if aliasFile.Path == "" || aliasFile.Path == "/" {
return nil, fmt.Errorf("missing command.files.path in alias manifest")
}
}
return alias, nil
}
func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
loadedAlias, ok := loadedAliases[ctx.Command.Name]
if !ok {
con.PrintErrorf("No alias found for `%s` command\n", ctx.Command.Name)
return
}
alias := loadedAlias.Manifest
binPath, err := alias.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
args := ctx.Args.StringList("arguments")
var extArgs string
if len(alias.DefaultArgs) != 0 && len(args) == 0 {
extArgs = alias.DefaultArgs
} else {
extArgs = strings.Join(args, " ")
}
entryPoint := alias.Entrypoint
processName := ctx.Flags.String("process")
if processName == "" {
processName, err = alias.getDefaultProcess(session.GetOS())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
isDLL := false
if filepath.Ext(binPath) == ".dll" {
isDLL = true
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
var outFilePath *os.File
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
if alias.IsAssembly {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: processName,
Arguments: extArgs,
Assembly: binData,
Arch: ctx.Flags.String("arch"),
Method: ctx.Flags.String("method"),
ClassName: ctx.Flags.String("class"),
AppDomain: ctx.Flags.String("app-domain"),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
if outFilePath != nil {
outFilePath.Write(executeAssemblyResp.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else if alias.IsReflective {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
Request: con.ActiveTarget.Request(ctx),
Args: strings.Trim(extArgs, " "),
Data: binData,
ProcessName: processName,
EntryPoint: alias.Entrypoint,
Kill: true,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(spawnDllResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
Request: con.ActiveTarget.Request(ctx),
Args: extArgs,
Data: binData,
EntryPoint: entryPoint,
ProcessName: processName,
Kill: true,
IsDLL: isDLL,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(sideloadResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
}
}
func cmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
}
}
return false
}
+75
View File
@@ -0,0 +1,75 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// AliasesRemoveCmd - Locally load a alias into the Sliver shell.
func AliasesRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("Extension name is required\n")
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
err := RemoveAliasByCommandName(name, con)
if err != nil {
con.PrintErrorf("Error removing alias: %s\n", err)
return
} else {
con.PrintInfof("Alias '%s' removed\n", name)
}
}
// RemoveAliasByCommandName - Remove an alias by command name
func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedAliases[commandName]; !ok {
return errors.New("alias not loaded")
}
delete(loadedAliases, commandName)
con.App.Commands().Remove(commandName)
extPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
err := os.RemoveAll(extPath)
if err != nil {
return err
}
return nil
}
+453
View File
@@ -0,0 +1,453 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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/base64"
"errors"
"fmt"
"net/url"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
type ArmoryIndex struct {
Aliases []*ArmoryPackage `json:"aliases"`
Extensions []*ArmoryPackage `json:"extensions"`
Bundles []*ArmoryBundle `json:"bundles"`
}
type ArmoryPackage struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
RepoURL string `json:"repo_url"`
PublicKey string `json:"public_key"`
IsAlias bool `json:"-"`
}
type ArmoryBundle struct {
Name string `json:"name"`
Packages []string `json:"packages"`
}
type ArmoryHTTPConfig struct {
IgnoreCache bool
ProxyURL *url.URL
Timeout time.Duration
DisableTLSValidation bool
}
type indexCacheEntry struct {
RepoURL string
Fetched time.Time
Index ArmoryIndex
LastErr error
}
type pkgCacheEntry struct {
RepoURL string
Fetched time.Time
Pkg ArmoryPackage
Sig minisign.Signature
Alias *alias.AliasManifest
Extension *extensions.ExtensionManifest
LastErr error
}
var (
// public key -> armoryCacheEntry
indexCache = sync.Map{}
// public key -> armoryPkgCacheEntry
pkgCache = sync.Map{}
cacheTime = time.Hour // This will kill a download if exceeded so needs to be large
defaultTimeout = 15 * time.Minute
)
// ArmoryCmd - The main armory command
func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
armoriesConfig := assets.GetArmoriesConfig()
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
clientConfig := parseArmoryHTTPConfig(ctx)
indexes := fetchIndexes(armoriesConfig, clientConfig)
if len(indexes) != len(armoriesConfig) {
con.Printf("errors!\n")
indexCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(indexCacheEntry)
if cacheEntry.LastErr != nil {
con.PrintErrorf("%s: %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
}
return true
})
} else {
con.Printf("done!\n")
}
if 0 < len(indexes) {
con.PrintInfof("Fetching package information ... ")
fetchPackageSignatures(indexes, clientConfig)
errorCount := 0
aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr != nil {
errorCount++
if errorCount == 0 {
con.Printf("errors!\n")
}
con.PrintErrorf("%s: %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
} else {
if cacheEntry.Pkg.IsAlias {
aliases = append(aliases, cacheEntry.Alias)
} else {
exts = append(exts, cacheEntry.Extension)
}
}
return true
})
if errorCount == 0 {
con.Printf("done!\n")
}
if 0 < len(aliases) || 0 < len(exts) {
con.Println()
PrintArmoryPackages(aliases, exts, con)
} else {
con.PrintInfof("No packages found\n")
}
con.Println()
bundles := bundlesInCache()
if 0 < len(bundles) {
PrintArmoryBundles(bundles, con)
} else {
con.PrintInfof("No bundles found\n")
}
} else {
con.PrintInfof("No indexes found\n")
}
}
func refresh(clientConfig ArmoryHTTPConfig) {
armoriesConfig := assets.GetArmoriesConfig()
indexes := fetchIndexes(armoriesConfig, clientConfig)
fetchPackageSignatures(indexes, clientConfig)
}
func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if cacheEntry.Pkg.IsAlias {
aliases = append(aliases, cacheEntry.Alias)
} else {
exts = append(exts, cacheEntry.Extension)
}
}
return true
})
return aliases, exts
}
func bundlesInCache() []*ArmoryBundle {
bundles := []*ArmoryBundle{}
indexCache.Range(func(key, value interface{}) bool {
indexBundles := value.(indexCacheEntry).Index.Bundles
bundles = append(bundles, indexBundles...)
return true
})
return bundles
}
// AliasExtensionOrBundleCompleter - Completer for alias, extension, and bundle names
func AliasExtensionOrBundleCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
results := []string{}
aliases, exts := packagesInCache()
bundles := bundlesInCache()
for _, alias := range aliases {
if strings.HasPrefix(alias.CommandName, prefix) {
results = append(results, alias.CommandName)
}
}
for _, extension := range exts {
if strings.HasPrefix(extension.CommandName, prefix) {
results = append(results, extension.CommandName)
}
}
for _, bundle := range bundles {
if strings.HasPrefix(bundle.Name, prefix) {
results = append(results, bundle.Name)
}
}
return results
}
// PrintArmoryPackages - Prints the armory packages
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverConsoleClient) {
type pkgInfo struct {
CommandName string
Version string
Type string
Help string
URL string
}
entries := []pkgInfo{}
for _, alias := range aliases {
entries = append(entries, pkgInfo{
CommandName: alias.CommandName,
Version: alias.Version,
Type: "Alias",
Help: alias.Help,
URL: alias.RepoURL,
})
}
for _, extension := range exts {
entries = append(entries, pkgInfo{
CommandName: extension.CommandName,
Version: extension.Version,
Type: "Extension",
Help: extension.Help,
URL: extension.RepoURL,
})
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Packages" + console.Normal)
tw.AppendHeader(table.Row{
"Command Name",
"Version",
"Type",
"Help",
"URL",
})
tw.SortBy([]table.SortBy{
{Name: "Command Name", Mode: table.Asc},
})
for _, pkg := range entries {
color := console.Normal
if extensions.CmdExists(pkg.CommandName, con.App) {
color = console.Green
}
tw.AppendRow(table.Row{
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),
})
}
con.Printf("%s\n", tw.Render())
}
// PrintArmoryBundles - Prints the armory bundles
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
tw.AppendHeader(table.Row{
"Name",
"Contains",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for _, bundle := range bundles {
tw.AppendRow(table.Row{
bundle.Name,
strings.Join(bundle.Packages, ", "),
})
}
con.Printf("%s\n", tw.Render())
}
func parseArmoryHTTPConfig(ctx *grumble.Context) ArmoryHTTPConfig {
var proxyURL *url.URL
rawProxyURL := ctx.Flags.String("proxy")
if rawProxyURL != "" {
proxyURL, _ = url.Parse(rawProxyURL)
}
timeout := defaultTimeout
rawTimeout := ctx.Flags.String("timeout")
if rawTimeout != "" {
var err error
timeout, err = time.ParseDuration(rawTimeout)
if err != nil {
timeout = defaultTimeout
}
}
return ArmoryHTTPConfig{
IgnoreCache: ctx.Flags.Bool("ignore-cache"),
ProxyURL: proxyURL,
Timeout: timeout,
DisableTLSValidation: ctx.Flags.Bool("insecure"),
}
}
// fetch armory indexes, only returns indexes that were fetched successfully
// errors are still in the cache objects however and can be checked
func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
wg := &sync.WaitGroup{}
for _, armoryConfig := range armoryConfigs {
wg.Add(1)
go fetchIndex(armoryConfig, clientConfig, wg)
}
wg.Wait()
indexes := []ArmoryIndex{}
indexCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(indexCacheEntry)
if cacheEntry.LastErr == nil {
indexes = append(indexes, cacheEntry.Index)
}
return true
})
return indexes
}
func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
defer wg.Done()
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
if ok {
cached := cacheEntry.(indexCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return
}
}
armoryResult := &indexCacheEntry{RepoURL: armoryConfig.RepoURL}
defer func() {
armoryResult.Fetched = time.Now()
indexCache.Store(armoryConfig.PublicKey, *armoryResult)
}()
repoURL, err := url.Parse(armoryConfig.RepoURL)
if err != nil {
armoryResult.LastErr = err
return
}
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
armoryResult.LastErr = errors.New("invalid URL scheme")
return
}
var index *ArmoryIndex
if indexParser, ok := indexParsers[repoURL.Hostname()]; ok {
index, err = indexParser(armoryConfig, clientConfig)
} else {
index, err = DefaultArmoryIndexParser(armoryConfig, clientConfig)
}
if index != nil {
armoryResult.Index = *index
}
armoryResult.LastErr = err
}
func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
wg := &sync.WaitGroup{}
for _, index := range indexes {
for _, armoryPkg := range index.Extensions {
wg.Add(1)
armoryPkg.IsAlias = false
go fetchPackageSignature(wg, armoryPkg, clientConfig)
}
for _, armoryPkg := range index.Aliases {
wg.Add(1)
armoryPkg.IsAlias = true
go fetchPackageSignature(wg, armoryPkg, clientConfig)
}
}
wg.Wait()
}
func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
defer wg.Done()
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
if ok {
cached := cacheEntry.(pkgCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return
}
}
pkgCacheEntry := &pkgCacheEntry{RepoURL: armoryPkg.RepoURL}
defer func() {
pkgCacheEntry.Fetched = time.Now()
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
}()
repoURL, err := url.Parse(armoryPkg.RepoURL)
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
pkgCacheEntry.LastErr = errors.New("invalid URL scheme")
return
}
var sig *minisign.Signature
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
} else {
sig, _, err = DefaultArmoryPkgParser(armoryPkg, true, clientConfig)
}
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if sig != nil {
pkgCacheEntry.Sig = *sig
} else {
pkgCacheEntry.LastErr = errors.New("nil signature")
return
}
if armoryPkg != nil {
pkgCacheEntry.Pkg = *armoryPkg
}
if err == nil {
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if armoryPkg.IsAlias {
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
} else {
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
}
pkgCacheEntry.LastErr = err
}
}
+1
View File
@@ -0,0 +1 @@
package armory
+276
View File
@@ -0,0 +1,276 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
)
var (
ErrPackageNotFound = errors.New("package not found")
)
// ArmoryInstallCmd - The armory install command
func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("A package or bundle name is required")
return
}
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
err := installPackageByName(name, clientConfig, con)
if err == nil {
return
}
if err == ErrPackageNotFound {
bundles := bundlesInCache()
for _, bundle := range bundles {
if bundle.Name == name {
installBundle(bundle, clientConfig, con)
return
}
}
}
con.PrintErrorf("No package or bundle named '%s' was found", name)
}
func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
for _, pkgName := range bundle.Packages {
err := installExtensionPackageByName(pkgName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
}
}
}
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
aliases, extensions := packagesInCache()
for _, alias := range aliases {
if alias.CommandName == name {
installAlias(alias, clientConfig, con)
return nil
}
}
for _, ext := range extensions {
if ext.CommandName == name {
installExtension(ext, clientConfig, con)
return nil
}
}
return ErrPackageNotFound
}
func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
err := installAliasPackageByName(alias.CommandName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
return
}
}
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return errors.New("package not found")
}
repoURL, err := url.Parse(entry.RepoURL)
if err != nil {
return err
}
var sig *minisign.Signature
var tarGz []byte
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
}
if err != nil {
return err
}
var publicKey minisign.PublicKey
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
rawSig, _ := sig.MarshalText()
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
if !valid {
return errors.New("signature verification failed")
}
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
if installPath == nil {
return errors.New("failed to install alias")
}
_, err = alias.LoadAlias(filepath.Join(*installPath, alias.ManifestFileName), con)
if err != nil {
return err
}
return nil
}
func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
deps := make(map[string]struct{})
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
for dep := range deps {
if extensions.CmdExists(dep, con.App) {
continue // Dependency is already installed
}
err := installExtensionPackageByName(dep, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
return
}
}
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
return
}
}
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return
}
if entry.Extension.DependsOn == "" {
return // Avoid adding empty dependency
}
if entry.Extension.DependsOn == name {
return // Avoid infinite loop of something that depends on itself
}
// We also need to look out for circular dependencies, so if we've already
// seen this dependency, we stop resolving
if _, ok := deps[entry.Extension.DependsOn]; ok {
return // Already resolved
}
if maxDepDepth < len(deps) {
return
}
deps[entry.Extension.DependsOn] = struct{}{}
resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con)
}
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return errors.New("package not found")
}
repoURL, err := url.Parse(entry.RepoURL)
if err != nil {
return err
}
var sig *minisign.Signature
var tarGz []byte
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
}
if err != nil {
return err
}
var publicKey minisign.PublicKey
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
rawSig, _ := sig.MarshalText()
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
if !valid {
return errors.New("signature verification failed")
}
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
if installPath == nil {
return errors.New("failed to install extension")
}
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
if err != nil {
return err
}
// do not add if the command already exists
if extensions.CmdExists(extCmd.Name, con.App) {
return fmt.Errorf("%s command already exists", extCmd.Name)
}
extensions.ExtensionRegisterCommand(extCmd, con)
return nil
}
+355
View File
@@ -0,0 +1,355 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/server/cryptography/minisign"
)
// ArmoryIndexParser - Generic interface to fetch armory indexes
type ArmoryIndexParser func(*assets.ArmoryConfig, ArmoryHTTPConfig) (*ArmoryIndex, error)
// ArmoryPackageParser - Generic interface to fetch armory package manifests
type ArmoryPackageParser func(*ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
var (
indexParsers = map[string]ArmoryIndexParser{
"api.github.com": GithubArmoryIndexParser,
}
pkgParsers = map[string]ArmoryPackageParser{
"api.github.com": GithubArmoryPackageParser,
}
)
const (
armoryIndexFileName = "armory.json"
armoryIndexSigFileName = "armory.minisig"
)
type armoryIndexResponse struct {
Minisig string `json:"minisig"` // Minisig
ArmoryIndex string `json:"armory_index"` // Base64 String
}
type armoryPkgResponse struct {
Minisig string `json:"minisig"` // Minisig
TarGzURL string `json:"tar_gz_url"`
}
// DefaultArmoryParser - Parse the armory index directly from the url
func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
if err != nil {
return nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryConfig.RepoURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code")
}
index := &armoryIndexResponse{}
err = json.Unmarshal(body, index)
if err != nil {
return nil, err
}
// Verify index is signed by trusted key
valid := minisign.Verify(publicKey, []byte(index.ArmoryIndex), []byte(index.ArmoryIndex))
if !valid {
return nil, errors.New("invalid signature")
}
armoryIndex := &ArmoryIndex{}
armoryIndexData, err := base64.StdEncoding.DecodeString(index.ArmoryIndex)
if err != nil {
return nil, err
}
err = json.Unmarshal(armoryIndexData, armoryIndex)
if err != nil {
return nil, err
}
return armoryIndex, nil
}
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
if err != nil {
return nil, nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
pkgResp := &armoryPkgResponse{}
err = json.Unmarshal(body, pkgResp)
if err != nil {
return nil, nil, err
}
sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
if err != nil {
return nil, nil, err
}
var tarGz []byte
if !sigOnly {
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
if tarGzURL.Scheme != "https" && tarGzURL.Scheme != "http" {
return nil, nil, errors.New("invalid url scheme")
}
resp, err := client.Get(tarGzURL.String())
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
tarGz, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
}
return sig, tarGz, nil
}
type GithubAsset struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Size int `json:"size"`
BrowserDownloadURL string `json:"browser_download_url"`
}
type GithubRelease struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TagName string `json:"tag_name"`
Body string `json:"body"`
Prerelease bool `json:"prerelease"`
TarballURL string `json:"tarball_url"`
ZipballURL string `json:"zipball_url"`
CreatedAt string `json:"created_at"`
PublishedAt string `json:"published_at"`
Assets []GithubAsset `json:"assets"`
}
// GithubArmoryIndexParser - Parse the armory index from a GitHub release
func GithubArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
if err != nil {
return nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryConfig.RepoURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code")
}
releases := []GithubRelease{}
err = json.Unmarshal(body, &releases)
if err != nil {
return nil, err
}
if len(releases) < 1 {
return nil, errors.New("no releases found")
}
release := releases[0] // Latest only right now
var armoryIndexData []byte
var sigData []byte
for _, asset := range release.Assets {
if asset.Name == armoryIndexFileName {
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
armoryIndexData, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
}
if asset.Name == armoryIndexSigFileName {
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
sigData, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
}
}
// Verify index is signed by trusted key
valid := minisign.Verify(publicKey, armoryIndexData, sigData)
if !valid {
return nil, errors.New("invalid signature")
}
armoryIndex := &ArmoryIndex{}
err = json.Unmarshal(armoryIndexData, armoryIndex)
if err != nil {
return nil, err
}
return armoryIndex, nil
}
// GithubArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
if err != nil {
return nil, nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
releases := []GithubRelease{}
err = json.Unmarshal(body, &releases)
if err != nil {
return nil, nil, err
}
release := releases[0] // Latest only right now
var sig *minisign.Signature
var tarGz []byte
for _, asset := range release.Assets {
if asset.Name == fmt.Sprintf("%s.minisig", armoryPkg.CommandName) {
var resp *http.Response
resp, err = client.Get(asset.BrowserDownloadURL)
if err != nil {
break
}
defer resp.Body.Close()
var body []byte
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
break
}
sig, err = parsePkgMinsig(body)
if err != nil {
break
}
}
if asset.Name == fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName) && !sigOnly {
var resp *http.Response
resp, err = client.Get(asset.BrowserDownloadURL)
if err != nil {
break
}
defer resp.Body.Close()
tarGz, err = ioutil.ReadAll(resp.Body)
if err != nil {
break
}
}
}
return sig, tarGz, err
}
func parsePkgMinsig(data []byte) (*minisign.Signature, error) {
var sig minisign.Signature
err := sig.UnmarshalText(data)
if err != nil {
return nil, err
}
if len(sig.TrustedComment) < 1 {
return nil, errors.New("missing trusted comment")
}
return &sig, nil
}
func httpClient(config ArmoryHTTPConfig) *http.Client {
return &http.Client{
Timeout: config.Timeout,
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: config.Timeout,
}).Dial,
Proxy: http.ProxyURL(config.ProxyURL),
TLSHandshakeTimeout: config.Timeout,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.DisableTLSValidation,
},
},
}
}
+65
View File
@@ -0,0 +1,65 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"regexp"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// ArmorySearchCmd - Search for packages by name
func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
con.Printf(console.Clearln + "\r")
rawNameExpr := ctx.Args.String("name")
if rawNameExpr == "" {
con.PrintErrorf("Please specify a search term!\n")
return
}
nameExpr, err := regexp.Compile(rawNameExpr)
if err != nil {
con.PrintErrorf("Invalid regular expression: %s\n", err)
return
}
aliases, exts := packagesInCache()
matchedAliases := []*alias.AliasManifest{}
for _, a := range aliases {
if nameExpr.MatchString(a.CommandName) {
matchedAliases = append(matchedAliases, a)
}
}
matchedExts := []*extensions.ExtensionManifest{}
for _, ext := range exts {
if nameExpr.MatchString(ext.CommandName) {
matchedExts = append(matchedExts, ext)
}
}
if len(matchedAliases) == 0 && len(matchedExts) == 0 {
con.PrintInfof("No packages found matching '%s'\n", rawNameExpr)
return
}
PrintArmoryPackages(matchedAliases, matchedExts, con)
}
+112
View File
@@ -0,0 +1,112 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// ArmoryUpdateCmd - Update all installed extensions/aliases
func ArmoryUpdateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
con.Printf(console.Clearln + "\r")
// Aliases
aliasUpdates := checkForAliasUpdates(clientConfig, con)
if 0 < len(aliasUpdates) {
con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
for _, aliasName := range aliasUpdates {
err := installAliasPackageByName(aliasName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
}
}
} else {
con.PrintInfof("All aliases up to date!\n")
}
// Extensions
extUpdates := checkForExtensionUpdates(clientConfig, con)
if 0 < len(extUpdates) {
con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
for _, extName := range extUpdates {
err := installExtensionPackageByName(extName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", extName, err)
}
}
} else {
con.PrintInfof("All extensions up to date!\n")
}
}
func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
cachedAliases, _ := packagesInCache()
results := []string{}
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
data, err := ioutil.ReadFile(aliasManifestPath)
if err != nil {
continue
}
localManifest, err := alias.ParseAliasManifest(data)
if err != nil {
continue
}
for _, latestAlias := range cachedAliases {
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
// the armory differs at all from the local version, the extension is out of date.
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
results = append(results, localManifest.CommandName)
}
}
}
return results
}
func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
_, cachedExtensions := packagesInCache()
results := []string{}
for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
data, err := ioutil.ReadFile(extManifestPath)
if err != nil {
continue
}
localManifest, err := extensions.ParseExtensionManifest(data)
if err != nil {
continue
}
for _, latestExt := range cachedExtensions {
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
// the armory differs at all from the local version, the extension is out of date.
if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version {
results = append(results, localManifest.CommandName)
}
}
}
return results
}
+369 -66
View File
@@ -17,7 +17,7 @@ package command
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
---
---------------------------------------------------------------------
This file contains all of the code that binds a given string/flags/etc. to a
command implementation function.
@@ -36,11 +36,12 @@ package command
import (
"os"
"github.com/bishopfox/sliver/client/command/socks"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/armory"
"github.com/bishopfox/sliver/client/command/backdoor"
"github.com/bishopfox/sliver/client/command/beacons"
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/dllhijack"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
@@ -53,7 +54,6 @@ import (
"github.com/bishopfox/sliver/client/command/jobs"
"github.com/bishopfox/sliver/client/command/kill"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/command/macros"
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/network"
"github.com/bishopfox/sliver/client/command/operators"
@@ -68,6 +68,7 @@ import (
"github.com/bishopfox/sliver/client/command/sessions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/command/shell"
"github.com/bishopfox/sliver/client/command/socks"
"github.com/bishopfox/sliver/client/command/tasks"
"github.com/bishopfox/sliver/client/command/update"
"github.com/bishopfox/sliver/client/command/use"
@@ -86,6 +87,7 @@ const (
// BindCommands - Bind commands to a App
func BindCommands(con *console.SliverConsoleClient) {
// Load Reactions
n, err := reaction.LoadReactions()
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("Failed to load reactions: %s\n", err)
@@ -93,16 +95,194 @@ func BindCommands(con *console.SliverConsoleClient) {
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
}
// Attempt to load extensions from ~/.sliver-client/extensions/
exts, err := extensions.ParseExtensions(assets.GetExtensionsDir())
// Absorb error in case there's no extensions manifest
if err == nil {
for _, ext := range exts {
extensions.RegisterExtensionCommand(ext, con)
// Load Aliases
aliasManifests := assets.GetInstalledAliasManifests()
n = 0
for _, manifest := range aliasManifests {
_, err = alias.LoadAlias(manifest, con)
if err != nil {
con.PrintErrorf("Failed to load alias: %s\n", err)
continue
}
n++
}
if 0 < n {
con.PrintInfof("Loaded %d alias(es) from disk\n", n)
}
// Load Extensions
extensionManifests := assets.GetInstalledExtensionManifests()
n = 0
for _, manifest := range extensionManifests {
ext, err := extensions.LoadExtensionManifest(manifest)
// Absorb error in case there's no extensions manifest
if err != nil {
con.PrintErrorf("Failed to load extension: %s\n", err)
continue
}
extensions.ExtensionRegisterCommand(ext, con)
n++
}
if 0 < n {
con.PrintInfof("Loaded %d extension(s) from disk\n", n)
}
con.App.SetPrintHelp(help.HelpCmd(con)) // Responsible for display long-form help templates, etc.
// [ Aliases ] ---------------------------------------------
aliasCmd := &grumble.Command{
Name: consts.AliasesStr,
Help: "List current aliases",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
con.App.AddCommand(aliasCmd)
aliasCmd.AddCommand(&grumble.Command{
Name: consts.LoadStr,
Help: "Load a command alias",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr, consts.LoadStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesLoadCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the alias directory")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
aliasCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install a command alias",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr, consts.InstallStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("path", "path to the alias directory or tar.gz file")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
aliasCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove an alias",
LongHelp: help.GetHelpFor([]string{consts.RmStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesRemoveCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "name of the alias to remove")
},
Completer: func(prefix string, args []string) []string {
return alias.AliasCommandNameCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
// [ Armory ] ---------------------------------------------
armoryCmd := &grumble.Command{
Name: consts.ArmoryStr,
Help: "Automatically download and install extensions/aliases",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
con.App.AddCommand(armoryCmd)
armoryCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install an alias or extension",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "name of the extension or alias to install")
},
Completer: func(prefix string, args []string) []string {
return armory.AliasExtensionOrBundleCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
armoryCmd.AddCommand(&grumble.Command{
Name: consts.UpdateStr,
Help: "Update installed an aliases and extensions",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.UpdateStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryUpdateCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
armoryCmd.AddCommand(&grumble.Command{
Name: consts.SearchStr,
Help: "Search for aliases and extensions by name (regex)",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.SearchStr}),
Args: func(a *grumble.Args) {
a.String("name", "a name regular expression")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmorySearchCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
// [ Update ] --------------------------------------------------------------
con.App.AddCommand(&grumble.Command{
@@ -286,7 +466,7 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Start a stager listener",
LongHelp: help.GetHelpFor([]string{consts.StageListenerStr}),
Flags: func(f *grumble.Flags) {
f.String("p", "profile", "", "Implant profile to link with the listener")
f.String("p", "profile", "", "Implant profile name to link with the listener")
f.String("u", "url", "", "URL to which the stager will call back to")
f.String("c", "cert", "", "path to PEM encoded certificate file (HTTPS only)")
f.String("k", "key", "", "path to PEM encoded private key file (HTTPS only)")
@@ -503,7 +683,7 @@ func BindCommands(con *console.SliverConsoleClient) {
return nil
},
Completer: func(prefix string, args []string) []string {
return use.Completer(con, prefix, args)
return use.BeaconAndSessionIDCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
}
@@ -804,6 +984,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Uint("p", "pid", 0, "Pid of process to inject into (0 means injection into ourselves)")
f.String("n", "process", `c:\windows\system32\notepad.exe`, "Process to inject into when running in interactive mode")
f.Bool("i", "interactive", false, "Inject into a new process and interact with it")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverHelpGroup,
@@ -817,8 +998,9 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("e", "entry-point", "", "Entrypoint for the DLL (Windows only)")
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
f.Bool("s", "save", false, "save output to file")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("filepath", "path the shared library file")
@@ -1073,7 +1255,7 @@ func BindCommands(con *console.SliverConsoleClient) {
})
generateCmd.AddCommand(&grumble.Command{
Name: consts.StagerStr,
Help: "Generate a implant stager using MSF",
Help: "Generate a stager using Metasploit (requires local Metasploit installation)",
LongHelp: help.GetHelpFor([]string{consts.StagerStr}),
Flags: func(f *grumble.Flags) {
f.String("o", "os", "windows", "operating system")
@@ -1084,6 +1266,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("f", "format", "raw", "Output format (msfvenom formats, see `help generate stager` for the list)")
f.String("b", "badchars", "", "bytes to exclude from stage shellcode")
f.String("s", "save", "", "directory to save the generated stager to")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1095,9 +1278,9 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.GenericHelpGroup,
})
generateCmd.AddCommand(&grumble.Command{
Name: consts.CompilerStr,
Name: consts.CompilerInfoStr,
Help: "Get information about the server's compiler",
LongHelp: help.GetHelpFor([]string{consts.CompilerStr}),
LongHelp: help.GetHelpFor([]string{consts.CompilerInfoStr}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1152,11 +1335,16 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Generate implant from a profile",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.GenerateStr}),
Flags: func(f *grumble.Flags) {
f.String("p", "name", "", "profile name")
f.String("s", "save", "", "directory/file to the binary to")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ProfileNameCompleter(prefix, args, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesGenerateCmd(ctx, con)
@@ -1165,32 +1353,35 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.GenericHelpGroup,
})
profilesCmd.AddCommand(&grumble.Command{
profilesNewCmd := &grumble.Command{
Name: consts.NewStr,
Help: "Save a new implant profile",
Help: "Create a new implant profile (interactive session)",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.NewStr}),
Flags: func(f *grumble.Flags) {
// Generate flags
f.String("o", "os", "windows", "operating system")
f.String("a", "arch", "amd64", "cpu architecture")
f.Bool("d", "debug", false, "enable debug features")
f.Bool("e", "evasion", false, "enable evasion features")
f.Bool("s", "skip-symbols", false, "skip symbol obfuscation")
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
f.String("m", "mtls", "", "mtls domain(s)")
f.String("g", "wg", "", "wg domain(s)")
f.String("H", "http", "", "http[s] domain(s)")
f.String("n", "dns", "", "dns domain(s)")
f.String("c", "canary", "", "canary domain(s)")
f.String("m", "mtls", "", "mtls connection strings")
f.String("g", "wg", "", "wg connection strings")
f.String("b", "http", "", "http(s) connection strings")
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
f.String("c", "canary", "", "canary domain(s)")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "attempt to poll every n second(s)")
f.String("w", "limit-datetime", "", "limit execution to before datetime")
f.Bool("x", "limit-domainjoined", false, "limit execution to domain joined machines")
@@ -1200,11 +1391,11 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
f.String("R", "profile-name", "", "profile name")
f.String("N", "name", "", "agent name")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesNewCmd(ctx, con)
@@ -1212,7 +1403,67 @@ func BindCommands(con *console.SliverConsoleClient) {
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
profilesCmd.AddCommand(profilesNewCmd)
// New Beacon Profile Command
profilesNewCmd.AddCommand(&grumble.Command{
Name: consts.BeaconStr,
Help: "Create a new implant profile (beacon)",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.NewStr, consts.BeaconStr}),
Flags: func(f *grumble.Flags) {
f.Int64("D", "days", 0, "beacon interval days")
f.Int64("H", "hours", 0, "beacon interval hours")
f.Int64("M", "minutes", 0, "beacon interval minutes")
f.Int64("S", "seconds", 60, "beacon interval seconds")
f.Int64("J", "jitter", 30, "beacon interval jitter in seconds")
// Generate flags
f.String("o", "os", "windows", "operating system")
f.String("a", "arch", "amd64", "cpu architecture")
f.Bool("d", "debug", false, "enable debug features")
f.Bool("e", "evasion", false, "enable evasion features")
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
f.String("c", "canary", "", "canary domain(s)")
f.String("m", "mtls", "", "mtls connection strings")
f.String("g", "wg", "", "wg connection strings")
f.String("b", "http", "", "http(s) connection strings")
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
f.String("w", "limit-datetime", "", "limit execution to before datetime")
f.Bool("x", "limit-domainjoined", false, "limit execution to domain joined machines")
f.String("y", "limit-username", "", "limit execution to specified username")
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesNewBeaconCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
profilesCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove a profile",
@@ -1221,7 +1472,10 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("profile-name", "name of the profile")
a.String("name", "name of the profile", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ProfileNameCompleter(prefix, args, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -1238,6 +1492,13 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "List implant builds",
LongHelp: help.GetHelpFor([]string{consts.ImplantBuildsStr}),
Flags: func(f *grumble.Flags) {
f.String("o", "os", "", "filter builds by operating system")
f.String("a", "arch", "", "filter builds by cpu architecture")
f.String("f", "format", "", "filter builds by artifact format")
f.Bool("s", "only-sessions", false, "filter interactive sessions")
f.Bool("b", "only-beacons", false, "filter beacons")
f.Bool("d", "no-debug", false, "filter builds by debug flag")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1256,7 +1517,10 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("implant-name", "implant name")
a.String("name", "implant name", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ImplantBuildNameCompleter(prefix, args, generate.ImplantBuildFilter{}, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -1861,24 +2125,6 @@ func BindCommands(con *console.SliverConsoleClient) {
})
con.App.AddCommand(beaconsCmd)
// [ Macros ] ---------------------------------------------
con.App.AddCommand(&grumble.Command{
Name: consts.LoadMacroStr,
Help: "Load a sliver macro",
LongHelp: help.GetHelpFor([]string{consts.LoadMacroStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
macros.LoadMacroCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the macro directory")
},
HelpGroup: consts.GenericHelpGroup,
})
// [ Environment ] ---------------------------------------------
envCmd := &grumble.Command{
@@ -2344,8 +2590,8 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
}
socksCmd.AddCommand(&grumble.Command{
Name: "add",
Help: "Create a new Socks5 Proxy",
Name: consts.StartStr,
Help: "Start an in-band SOCKS5 proxy",
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.String("H", "host", "127.0.0.1", "Bind a Socks5 Host")
@@ -2354,15 +2600,15 @@ func BindCommands(con *console.SliverConsoleClient) {
},
Run: func(ctx *grumble.Context) error {
con.Println()
socks.SocksAddCmd(ctx, con)
socks.SocksStartCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
socksCmd.AddCommand(&grumble.Command{
Name: "rm",
Help: "Remove a Socks5 Proxy",
Name: consts.StopStr,
Help: "Stop a SOCKS5 proxy",
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
@@ -2370,7 +2616,7 @@ func BindCommands(con *console.SliverConsoleClient) {
},
Run: func(ctx *grumble.Context) error {
con.Println()
socks.SocksRmCmd(ctx, con)
socks.SocksStopCmd(ctx, con)
con.Println()
return nil
},
@@ -2701,7 +2947,7 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Get Privs ] -----------------------------------------------------------------
getprivsCmd := &grumble.Command{
Name: consts.GetPrivsStr,
Help: "Get current privileges (Windows)",
Help: "Get current privileges (Windows only)",
LongHelp: help.GetHelpFor([]string{consts.GetPrivsStr}),
HelpGroup: consts.SliverWinHelpGroup,
Run: func(ctx *grumble.Context) error {
@@ -2718,13 +2964,13 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Extensions ] -----------------------------------------------------------------
extensionCmd := &grumble.Command{
Name: consts.ExtensionStr,
Help: "Manage sliver extensions",
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr}),
Name: consts.ExtensionsStr,
Help: "Manage extensions",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ListCmd(ctx, con)
extensions.ExtensionsCmd(ctx, con)
con.Println()
return nil
},
@@ -2732,25 +2978,82 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
}
extensionCmd.AddCommand(&grumble.Command{
Name: consts.ListStr,
Help: "List extensions loaded in the current session or beacon",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
HelpGroup: consts.SliverHelpGroup,
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsListCmd(ctx, con)
con.Println()
return nil
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.LoadStr,
Help: "Register a new extension",
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr, consts.LoadStr}),
Help: "Temporarily load an extension from a local directory",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.LoadStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.LoadCmd(ctx, con)
extensions.ExtensionLoadCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the extension directory")
},
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install an extension from a local directory or .tar.gz file",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.InstallStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("path", "path to the extension .tar.gz or directory")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove an installed extension",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.RmStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsRemoveCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "the command name of the extension to remove")
},
Completer: func(prefix string, args []string) []string {
return extensions.ExtensionsCommandNameCompleter(prefix, args, con)
},
})
con.App.AddCommand(extensionCmd)
// [ Prelude's Operator ] ------------------------------------------------------------
operatorCmd := &grumble.Command{
Name: consts.PreludeOperatorStr,
+4
View File
@@ -0,0 +1,4 @@
Completers
===========
This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
+63
View File
@@ -0,0 +1,63 @@
package completers
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
)
// LocalPathCompleter - Completes a local file system path
func LocalPathCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
var parent string
var partial string
fi, err := os.Stat(prefix)
if os.IsNotExist(err) {
parent = filepath.Dir(prefix)
partial = filepath.Base(prefix)
} else {
if fi.IsDir() {
parent = prefix
partial = ""
} else {
parent = filepath.Dir(prefix)
partial = filepath.Base(prefix)
}
}
results := []string{}
ls, err := ioutil.ReadDir(parent)
if err != nil {
return results
}
for _, fi = range ls {
if 0 < len(partial) {
if strings.HasPrefix(fi.Name(), partial) {
results = append(results, filepath.Join(parent, fi.Name()))
}
} else {
results = append(results, filepath.Join(parent, fi.Name()))
}
}
return results
}
+22 -17
View File
@@ -1,46 +1,51 @@
Extensions
====
===========
Allows to load and execute 3rd party extensions.
Extensions must be loaded from a directory with the following architecture:
Extensions directory structures can be arbitrary, however in the root of the directory or .tar.gz there
must be a `extension.json` or a `alias.json` file. All paths are relative to the manifest/root directory,
parent directories are not allowed. Only files listed in the manifest are copied, any other files will
be ignored.
```shell
/path/to/extension/folder/
├── manifest.json
├── extension.json
└── windows
├── 386
│   └── extension.x86.dll
└── amd64
└── extension.x64.dll
│ └── extension.x86.dll
│ └── extension.x64.dll
└── linux
│ └── extension.x86.so
│ └── extension.x64.so
└── darwin
└── extension.x86.dylib
└── extension.x64.dylib
```
The extension folder structure must follow the `GOOS/GOARCH/` scheme.
Here's an example manifest:
Here's an example manifest (i.e., the `extension.json` or a `alias.json`):
```json
[
{
"name": "foo",
"version": "1.0.0",
"extension_author": "ac1d-burn",
"original_author": "zer0-cool",
"repo_url": "https://github.com/foo/bar",
"help": "Help for foo command",
"entrypoint": "RunFoo",
"init" :"NimMain",
"dependsOn": "bar",
"depends_on": "bar",
"files": [
{
"os": "windows",
"files":{
"x86": "extension.x86.o",
"x64": "extension.x64.o",
}
"arch": "amd64",
"path": "extension.x64.o",
}
],
"arguments": [
{"name": "pid", "type": "int", "desc": "pid", "optional": false},
]
}
]
```
The structure is the following one:
+121
View File
@@ -0,0 +1,121 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"io/ioutil"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
)
// ExtensionsCmd - List information about installed extensions
func ExtensionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
PrintExtensions(con)
}
// PrintExtensions - Print a list of loaded extensions
func PrintExtensions(con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Command Name",
"Platforms",
"Version",
"Installed",
"Extension Author",
"Original Author",
"Repository",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
tw.SetColumnConfigs([]table.ColumnConfig{
{Number: 5, Align: text.AlignCenter},
})
installedManifests := getInstalledManifests()
for _, extension := range loadedExtensions {
installed := ""
if _, ok := installedManifests[extension.CommandName]; ok {
installed = "✅"
}
tw.AppendRow(table.Row{
extension.Name,
extension.CommandName,
strings.Join(extensionPlatforms(extension), ",\n"),
extension.Version,
installed,
extension.ExtensionAuthor,
extension.OriginalAuthor,
extension.RepoURL,
})
}
con.Println(tw.Render())
}
func extensionPlatforms(extension *ExtensionManifest) []string {
platforms := map[string]string{}
for _, entry := range extension.Files {
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
}
keys := []string{}
for key := range platforms {
keys = append(keys, key)
}
return keys
}
func getInstalledManifests() map[string]*ExtensionManifest {
manifestPaths := assets.GetInstalledExtensionManifests()
installedManifests := map[string]*ExtensionManifest{}
for _, manifestPath := range manifestPaths {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
continue
}
manifest := &ExtensionManifest{}
err = json.Unmarshal(data, manifest)
if err != nil {
continue
}
installedManifests[manifest.CommandName] = manifest
}
return installedManifests
}
// ExtensionsCommandNameCompleter - Completer for installed extensions command names
func ExtensionsCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
installedManifests := getInstalledManifests()
results := []string{}
for _, manifest := range installedManifests {
if strings.HasPrefix(manifest.CommandName, prefix) {
results = append(results, manifest.CommandName)
}
}
return results
}
@@ -0,0 +1,245 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"testing"
)
const (
sample1 = `{
"name": "test1",
"command_name": "test1",
"version": "1.0.0",
"extension_author": "test",
"original_author": "test",
"repo_url": "https://example.com/",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
sample2 = `{
"name": "test2",
"command_name": "test2",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "../../../../foo/test1.dll"
}
]
}`
)
func TestParseExtensionManifest(t *testing.T) {
extManifest, err := ParseExtensionManifest([]byte(sample1))
if err != nil {
t.Fatalf("Error parsing extension manifest: %s", err)
}
if extManifest.Name != "test1" {
t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name)
}
if extManifest.CommandName != "test1" {
t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName)
}
if extManifest.Version != "1.0.0" {
t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version)
}
if extManifest.ExtensionAuthor != "test" {
t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor)
}
if extManifest.OriginalAuthor != "test" {
t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor)
}
if extManifest.RepoURL != "https://example.com/" {
t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL)
}
if extManifest.Help != "some help" {
t.Errorf("Expected help 'some help', got '%s'", extManifest.Help)
}
if len(extManifest.Files) != 1 {
t.Errorf("Expected 1 file, got %d", len(extManifest.Files))
}
if extManifest.Files[0].OS != "windows" {
t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS)
}
if extManifest.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch)
}
if extManifest.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path)
}
extManifest2, err := ParseExtensionManifest([]byte(sample2))
if err != nil {
t.Fatalf("Error parsing extension manifest (2): %s", err)
}
if extManifest2.Name != "test2" {
t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name)
}
if extManifest2.CommandName != "test2" {
t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName)
}
if extManifest2.Help != "some help" {
t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help)
}
if len(extManifest2.Files) != 1 {
t.Errorf("Expected 1 file, got %d", len(extManifest2.Files))
}
if extManifest2.Files[0].OS != "windows" {
t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS)
}
if extManifest2.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
}
if extManifest2.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
}
}
const (
sample3 = `{
"name": "test3",
"command_name": "test3",
"version": "1.0.0",
"extension_author": "test",
"original_author": "test",
"repo_url": "https://example.com/",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
)
func TestParseExtensionManifestErrors(t *testing.T) {
sample3, err := ParseExtensionManifest([]byte(sample3))
if err != nil {
t.Fatalf("Failed to parse initial sample3: %s", err)
}
missingName := (*sample3)
missingName.Name = ""
data, _ := json.Marshal(missingName)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing name error, got none")
}
missingCmdName := (*sample3)
missingCmdName.CommandName = ""
data, _ = json.Marshal(missingCmdName)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing command name error, got none")
}
missingHelp := (*sample3)
missingHelp.Help = ""
data, _ = json.Marshal(missingHelp)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing help error, got none")
}
missingFiles := (*sample3)
missingFiles.Files = []*extensionFile{}
data, _ = json.Marshal(missingFiles)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files error, got none")
}
missingFileOS := (*sample3)
missingFileOS.Files = []*extensionFile{
{
OS: "",
Arch: "amd64",
Path: "foo/test1.dll",
},
}
data, _ = json.Marshal(missingFileOS)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.os error, got none")
}
missingFileArch := (*sample3)
missingFileArch.Files = []*extensionFile{
{
OS: "windows",
Arch: "",
Path: "foo/test1.dll",
},
}
data, _ = json.Marshal(missingFileArch)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.arch error, got none")
}
missingFilePath := (*sample3)
missingFilePath.Files = []*extensionFile{
{
OS: "windows",
Arch: "amd64",
Path: "",
},
}
data, _ = json.Marshal(missingFilePath)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.path error, got none")
}
invalidPaths := []string{
"../../../../../",
"/../../../../..",
".",
"/",
}
for _, invalidPath := range invalidPaths {
missingFilePath2 := (*sample3)
missingFilePath2.Files = []*extensionFile{
{
OS: "windows",
Arch: "amd64",
Path: invalidPath,
},
}
data, _ = json.Marshal(missingFilePath2)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.path error, got none")
}
}
}
+168
View File
@@ -0,0 +1,168 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// ExtensionsInstallCmd - Install an extension
func ExtensionsInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
extLocalPath := ctx.Args.String("path")
fi, err := os.Stat(extLocalPath)
if os.IsNotExist(err) {
con.PrintErrorf("Extension path '%s' does not exist", extLocalPath)
return
}
if !fi.IsDir() {
InstallFromFilePath(extLocalPath, false, con)
} else {
installFromDir(extLocalPath, con)
}
}
// Install an extension from a directory
func installFromDir(extLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := ioutil.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
return
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Extension '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nError creating extension directory: %s\n", err)
return
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
forceRemoveAll(installPath)
return
}
}
}
}
// Install an extension from a .tar.gz file
func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err)
return nil
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
return nil
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil
}
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nFailed to create extension directory: %s\n", err)
return nil
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return nil
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
if err != nil {
con.PrintErrorf("\nFailed to install file: %s\n", err)
forceRemoveAll(installPath)
return nil
}
}
}
con.Printf("done!\n")
return &installPath
}
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(extGzFilePath, artifactPath)
if err != nil {
return err
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
os.MkdirAll(artifactDir, 0o700)
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
+54
View File
@@ -0,0 +1,54 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// ExtensionsListCmd - List all extension loaded on the active session/beacon
func ExtensionsListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if extList.Response != nil && extList.Response.Err != "" {
con.PrintErrorf("%s\n", extList.Response.Err)
return
}
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
}
}
+131 -103
View File
@@ -1,5 +1,23 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"context"
@@ -12,38 +30,45 @@ import (
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
ManifestFileName = "extension.json"
)
var commandMap map[string]extensionCommand
var loadedExtensions = map[string]*ExtensionManifest{}
type extensionCommand struct {
Name string `json:"name"`
Help string `json:"help"`
Files []extensionFiles `json:"extFiles"`
Arguments []extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"dependsOn"`
Init string `json:"init"`
Path string
}
type binFiles struct {
Ext64Path string `json:"x64"`
Ext32Path string `json:"x86"`
type ExtensionManifest struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
Version string `json:"version"`
ExtensionAuthor string `json:"extension_author"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
Files []*extensionFile `json:"files"`
Arguments []*extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"depends_on"`
Init string `json:"init"`
RootPath string `json:"-"`
}
type extensionFiles struct {
OS string `json:"os"`
Files binFiles `json:"files"`
type extensionFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type extensionArgument struct {
@@ -53,66 +78,98 @@ type extensionArgument struct {
Optional bool `json:"optional"`
}
func (e *extensionCommand) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
for _, ef := range e.Files {
if targetOS == ef.OS {
switch targetArch {
case "386":
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext32Path)
case "amd64":
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
default:
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
}
func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
filePath = path.Join(assets.GetExtensionsDir(), cmdName, extFile.Path)
break
}
}
if filePath == "" {
err = fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
return "", err
}
return
if _, err := os.Stat(filePath); os.IsNotExist(err) {
err = fmt.Errorf("extension file not found: %s", filePath)
return "", err
}
return filePath, nil
}
func LoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// ExtensionLoadCmd - Load extension command
func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
extCmds, err := ParseExtensions(dirPath)
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
if err != nil {
return
}
// do not add if the command already exists
if CmdExists(extCmd.Name, con.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
return
}
ExtensionRegisterCommand(extCmd, con)
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
}
for _, extCmd := range extCmds {
// do not add if the command already exists
if cmdExists(extCmd.Name, con.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
continue
// ParseExtensions - Parse extension files
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extManifest, err := ParseExtensionManifest(data)
if err != nil {
return nil, err
}
extManifest.RootPath = filepath.Dir(manifestPath)
loadedExtensions[extManifest.CommandName] = extManifest
return extManifest, nil
}
// ParseExtensionManifest - Parse extension manifest from buffer
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
extManifest := &ExtensionManifest{}
err := json.Unmarshal(data, &extManifest)
if err != nil {
return nil, err
}
if extManifest.Name == "" {
return nil, errors.New("missing `name` field in extension manifest")
}
if extManifest.CommandName == "" {
return nil, errors.New("missing `command_name` field in extension manifest")
}
if len(extManifest.Files) == 0 {
return nil, errors.New("missing `files` field in extension manifest")
}
for _, extFiles := range extManifest.Files {
if extFiles.OS == "" {
return nil, errors.New("missing `files.os` field in extension manifest")
}
RegisterExtensionCommand(extCmd, con)
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
if extFiles.Arch == "" {
return nil, errors.New("missing `files.arch` field in extension manifest")
}
extFiles.Path = util.ResolvePath(extFiles.Path)
if extFiles.Path == "" || extFiles.Path == "/" {
return nil, errors.New("missing `files.path` field in extension manifest")
}
extFiles.OS = strings.ToLower(extFiles.OS)
extFiles.Arch = strings.ToLower(extFiles.Arch)
}
if extManifest.Help == "" {
return nil, errors.New("missing `help` field in extension manifest")
}
return extManifest, nil
}
func ParseExtensions(extPath string) ([]*extensionCommand, error) {
manifestPath := filepath.Join(extPath, "manifest.json")
jsonBytes, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extensionsCmds := make([]*extensionCommand, 0)
err = json.Unmarshal(jsonBytes, &extensionsCmds)
if err != nil {
return nil, err
}
for _, extCmd := range extensionsCmds {
extCmd.Path = extPath
commandMap[extCmd.Name] = *extCmd
}
return extensionsCmds, nil
}
func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConsoleClient) {
commandMap[extCmd.Name] = *extCmd
// ExtensionRegisterCommand - Register a new extension command
func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverConsoleClient) {
loadedExtensions[extCmd.CommandName] = extCmd
helpMsg := extCmd.Help
extensionCmd := &grumble.Command{
Name: extCmd.Name,
Name: extCmd.CommandName,
Help: helpMsg,
Run: func(extCtx *grumble.Context) error {
con.Println()
@@ -155,33 +212,7 @@ func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConso
con.App.AddCommand(extensionCmd)
}
func ListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %s", err)
return
}
if extList.Response != nil && extList.Response.Err != "" {
con.PrintErrorf("Error: %s", extList.Response.Err)
return
}
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
}
}
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *extensionCommand) error {
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *ExtensionManifest) error {
var extensionList []string
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
if err != nil {
@@ -193,7 +224,7 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return err
}
if extList.Response != nil && extList.Response.Err != "" {
@@ -240,14 +271,14 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return err
}
}
return nil
}
func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
func registerExtension(con *console.SliverConsoleClient, ext *ExtensionManifest, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: ext.Name,
Data: binData,
@@ -265,7 +296,7 @@ func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand,
}
func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *grumble.Context, depName string) error {
depExt, f := commandMap[depName]
depExt, f := loadedExtensions[depName]
if f {
depBinPath, err := depExt.getFileForTarget(depExt.Name, session.OS, session.Arch)
if err != nil {
@@ -275,7 +306,7 @@ func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *g
if err != nil {
return err
}
return registerExtension(con, &depExt, depBinData, session, ctx)
return registerExtension(con, depExt, depBinData, session, ctx)
}
return fmt.Errorf("missing dependency %s", depName)
}
@@ -293,13 +324,13 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
ext, ok := commandMap[ctx.Command.Name]
ext, ok := loadedExtensions[ctx.Command.Name]
if !ok {
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
return
}
if err = loadExtension(ctx, session, con, &ext); err != nil {
if err = loadExtension(ctx, session, con, ext); err != nil {
con.PrintErrorf("Could not load extension: %s\n", err)
return
}
@@ -334,7 +365,7 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
extName = ext.DependsOn
entryPoint = commandMap[extName].Entrypoint // should exist at this point
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
} else {
// Regular DLL
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
@@ -354,11 +385,11 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return
}
if callExtension.Response != nil && callExtension.Response.Err != "" {
con.PrintErrorf("Error: %s\n", callExtension.Response.Err)
con.PrintErrorf("%s\n", callExtension.Response.Err)
return
}
con.PrintInfof("Successfully executed %s\n", extName)
@@ -371,7 +402,7 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
}
func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]byte, error) {
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
var extensionArgs []byte
binData, err := ioutil.ReadFile(binPath)
if err != nil {
@@ -449,7 +480,8 @@ func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]b
}
func cmdExists(name string, app *grumble.App) bool {
// CmdExists - checks if a command exists
func CmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
@@ -457,7 +489,3 @@ func cmdExists(name string, app *grumble.App) bool {
}
return false
}
func init() {
commandMap = make(map[string]extensionCommand)
}
+77
View File
@@ -0,0 +1,77 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// ExtensionsRemoveCmd - Remove an extension
func ExtensionsRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("Extension name is required\n")
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
err := RemoveExtensionByCommandName(name, con)
if err != nil {
con.PrintErrorf("Error removing extension: %s\n", err)
return
} else {
con.PrintInfof("Extension '%s' removed\n", name)
}
}
// RemoveExtensionByCommandName - Remove an extension by command name
func RemoveExtensionByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedExtensions[commandName]; !ok {
return errors.New("extension not loaded")
}
delete(loadedExtensions, commandName)
con.App.Commands().Remove(commandName)
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
forceRemoveAll(extPath)
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
+31 -43
View File
@@ -1,16 +1,15 @@
package generate
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// CanariesCmd - Display canaries from the database and their status
@@ -21,56 +20,45 @@ func CanariesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if 0 < len(canaries.Canaries) {
displayCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
PrintCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
} else {
con.PrintInfof("No canaries in database\n")
}
}
func displayCanaries(con *console.SliverConsoleClient, canaries []*clientpb.DNSCanary, burnedOnly bool) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Sliver Name\tDomain\tTriggered\tFirst Trigger\tLatest Trigger\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Sliver Name")),
strings.Repeat("=", len("Domain")),
strings.Repeat("=", len("Triggered")),
strings.Repeat("=", len("First Trigger")),
strings.Repeat("=", len("Latest Trigger")),
)
lineColors := []string{}
// PrintCanaries - Print the canaries tracked by the server
func PrintCanaries(con *console.SliverConsoleClient, canaries []*clientpb.DNSCanary, burnedOnly bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Sliver Name",
"Domain",
"Triggered",
"First Trigger",
"Latest Trigger",
})
for _, canary := range canaries {
if burnedOnly && !canary.Triggered {
continue
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
canary.ImplantName,
canary.Domain,
fmt.Sprintf("%v", canary.Triggered),
canary.FirstTriggered,
canary.LatestTrigger,
)
lineColor := console.Normal
if canary.Triggered {
lineColors = append(lineColors, console.Bold+console.Red)
} else {
lineColors = append(lineColors, console.Normal)
}
}
table.Flush()
for index, line := range strings.Split(outputBuf.String(), "\n") {
if len(line) == 0 {
continue
}
// We need to account for the two rows of column headers
if 0 < len(line) && 2 <= index {
lineColor := lineColors[index-2]
con.Printf("%s%s%s\n", lineColor, line, console.Normal)
} else {
con.Printf("%s\n", line)
lineColor = console.Bold + console.Red
}
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)
}
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),
firstTrigger,
latestTrigger,
}
tw.AppendRow(row)
}
con.Printf("%s\n", tw.Render())
}
+19 -1
View File
@@ -2,7 +2,9 @@ package generate
import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/desertbit/grumble"
@@ -10,8 +12,24 @@ import (
// ImplantsRmCmd - Deletes an archived implant build from the server
func ImplantsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No name specified\n")
return
}
build := ImplantBuildByName(name, con)
if build == nil {
con.PrintErrorf("No implant build found with name '%s'\n", name)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' build?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
_, err := con.Rpc.DeleteImplantBuild(context.Background(), &clientpb.DeleteReq{
Name: ctx.Args.String("implant-name"),
Name: name,
})
if err != nil {
con.PrintErrorf("Failed to delete implant %s\n", err)
+115 -38
View File
@@ -19,19 +19,29 @@ package generate
*/
import (
"bytes"
"context"
"fmt"
"io"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// ImplantBuildFilter - Filter implant builds
type ImplantBuildFilter struct {
GOOS string
GOARCH string
Beacon bool
Session bool
Format string
Debug bool
}
// ImplantsCmd - Displays archived implant builds
func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
@@ -39,51 +49,118 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
implantBuildfilters := ImplantBuildFilter{}
if 0 < len(builds.Configs) {
displayAllImplantBuilds(con.App.Stdout(), builds.Configs)
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
} else {
con.PrintInfof("No implant builds\n")
}
}
func displayAllImplantBuilds(stdout io.Writer, configs map[string]*clientpb.ImplantConfig) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Name\tOS/Arch\tDebug\tFormat\tCommand & Control\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("OS/Arch")),
strings.Repeat("=", len("Debug")),
strings.Repeat("=", len("Format")),
strings.Repeat("=", len("Command & Control")),
)
// PrintImplantBuilds - Print the implant builds on the server
func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters ImplantBuildFilter, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Implant Type",
"OS/Arch",
"Format",
"Command & Control",
"Debug",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for sliverName, config := range configs {
if 0 < len(config.C2) {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
sliverName,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
fmt.Sprintf("%v", config.Debug),
config.Format,
fmt.Sprintf("[1] %s", config.C2[0].URL),
)
if filters.GOOS != "" && config.GOOS != filters.GOOS {
continue
}
if 1 < len(config.C2) {
for index, c2 := range config.C2[1:] {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
"",
"",
"",
"",
fmt.Sprintf("[%d] %s", index+2, c2.URL),
)
}
if filters.GOARCH != "" && config.GOARCH != filters.GOARCH {
continue
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n", "", "", "", "", "")
if filters.Beacon && !config.IsBeacon {
continue
}
if filters.Session && config.IsBeacon {
continue
}
if filters.Debug && config.Debug {
continue
}
if filters.Format != "" && (!strings.EqualFold(config.Format.String(), filters.Format) ||
!strings.HasPrefix(strings.ToLower(config.Format.String()), strings.ToLower(filters.Format))) {
continue
}
implantType := "session"
if config.IsBeacon {
implantType = "beacon"
}
c2URLs := []string{}
for index, c2 := range config.C2 {
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
}
tw.AppendRow(table.Row{
sliverName,
implantType,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
config.Format,
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
})
}
table.Flush()
fmt.Fprintf(stdout, "%s", outputBuf.String())
con.Println(tw.Render())
}
// ImplantBuildNameCompleter - Completer for implant build names
func ImplantBuildNameCompleter(prefix string, args []string, filters ImplantBuildFilter, con *console.SliverConsoleClient) []string {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
return []string{}
}
results := []string{}
for name, config := range builds.Configs {
if filters.GOOS != "" && config.GOOS != filters.GOOS {
continue
}
if filters.GOARCH != "" && config.GOARCH != filters.GOARCH {
continue
}
if filters.Beacon && !config.IsBeacon {
continue
}
if filters.Session && config.IsBeacon {
continue
}
if filters.Debug && config.Debug {
continue
}
if filters.Format != "" && !strings.EqualFold(config.Format.String(), filters.Format) {
continue
}
if strings.HasPrefix(name, prefix) {
results = append(results, name)
}
}
sort.StringSlice(results).Sort()
return results
}
// ImplantBuildByName - Get an implant build by name
func ImplantBuildByName(name string, con *console.SliverConsoleClient) *clientpb.ImplantConfig {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
return nil
}
for _, build := range builds.Configs {
if build.Name == name {
return build
}
}
return nil
}
+2 -2
View File
@@ -30,9 +30,9 @@ import (
// ProfilesGenerateCmd - Generate an implant binary based on a profile
func ProfilesGenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Flags.String("name")
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("no profile selected")
con.PrintErrorf("No profile name specified\n")
return
}
save := ctx.Flags.String("save")
+48 -6
View File
@@ -1,5 +1,22 @@
package generate
/*
Sliver Implant Framework
Copyright (C) 2021 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"
@@ -10,11 +27,7 @@ import (
// ProfilesNewCmd - Create a new implant profile
func ProfilesNewCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Flags.String("profile-name")
if name == "" {
con.PrintErrorf("Invalid profile name\n")
return
}
name := ctx.Args.String("name")
config := parseCompileFlags(ctx, con)
if config == nil {
return
@@ -27,6 +40,35 @@ func ProfilesNewCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.PrintInfof("Saved new profile %s\n", resp.Name)
con.PrintInfof("Saved new implant profile %s\n", resp.Name)
}
}
// ProfilesNewBeaconCmd - Create a new beacon profile
func ProfilesNewBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No profile name specified\n")
return
}
config := parseCompileFlags(ctx, con)
if config == nil {
return
}
config.IsBeacon = true
err := parseBeaconFlags(ctx, con, config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
profile := &clientpb.ImplantProfile{
Name: name,
Config: config,
}
resp, err := con.Rpc.SaveImplantProfile(context.Background(), profile)
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
}
}
+37 -1
View File
@@ -1,8 +1,28 @@
package generate
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/desertbit/grumble"
@@ -10,8 +30,24 @@ import (
// ProfilesRmCmd - Delete an implant profile
func ProfilesRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No profile name specified\n")
return
}
profile := GetImplantProfileByName(name, con)
if profile == nil {
con.PrintErrorf("No profile found with name '%s'\n", name)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' profile?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
_, err := con.Rpc.DeleteImplantProfile(context.Background(), &clientpb.DeleteReq{
Name: ctx.Args.String("profile-name"),
Name: name,
})
if err != nil {
con.PrintErrorf("Failed to delete profile %s\n", err)
+65 -47
View File
@@ -21,15 +21,15 @@ package generate
import (
"context"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// ProfilesCmd - Display implant profiles
@@ -39,72 +39,75 @@ func ProfilesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if len(profiles) == 0 {
con.PrintInfof("No profiles, create one with `%s`\n", consts.NewStr)
con.PrintInfof("No profiles, see `%s %s help`\n", consts.ProfilesStr, consts.NewStr)
return
} else {
PrintProfiles(profiles, con)
}
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Name\tPlatform\tCommand & Control\tDebug\tFormat\tObfuscation\tLimitations\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("Platform")),
strings.Repeat("=", len("Command & Control")),
strings.Repeat("=", len("Debug")),
strings.Repeat("=", len("Format")),
strings.Repeat("=", len("Obfuscation")),
strings.Repeat("=", len("Limitations")),
)
}
// PrintProfiles - Print the profiles
func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Profile Name",
"Implant Type",
"Platform",
"Command & Control",
"Debug",
"Format",
"Obfuscation",
"Limitations",
})
tw.SortBy([]table.SortBy{
{Name: "Profile Name", Mode: table.Asc},
})
for _, profile := range profiles {
config := profile.Config
if 0 < len(config.C2) {
obfuscation := "strings only"
if config.ObfuscateSymbols {
obfuscation = "symbols obfuscation"
}
if config.Debug {
obfuscation = "none"
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
profile.Name,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
fmt.Sprintf("[1] %s", config.C2[0].URL),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf("%v", config.Format),
obfuscation,
getLimitsString(config),
)
obfuscation := "disabled"
if config.ObfuscateSymbols {
obfuscation = "enabled"
}
if 1 < len(config.C2) {
for index, c2 := range config.C2[1:] {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
"",
"",
fmt.Sprintf("[%d] %s", index+2, c2.URL),
"",
"",
"",
"",
)
}
implantType := "session"
if config.IsBeacon {
implantType = "beacon"
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "", "", "", "", "", "", "")
c2URLs := []string{}
for index, c2 := range config.C2 {
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
}
tw.AppendRow(table.Row{
profile.Name,
implantType,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf("%v", config.Format),
obfuscation,
getLimitsString(config),
})
}
table.Flush()
con.Printf("%s\n", tw.Render())
}
func getImplantProfiles(con *console.SliverConsoleClient) []*clientpb.ImplantProfile {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Error %s", err)
con.PrintErrorf("%s\n", err)
return nil
}
return pbProfiles.Profiles
}
// GetImplantProfileByName - Get an implant profile by a specific name
func GetImplantProfileByName(name string, con *console.SliverConsoleClient) *clientpb.ImplantProfile {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Error %s", err)
con.PrintErrorf("%s\n", err)
return nil
}
for _, profile := range pbProfiles.Profiles {
@@ -114,3 +117,18 @@ func GetImplantProfileByName(name string, con *console.SliverConsoleClient) *cli
}
return nil
}
// ProfileNameCompleter - Completer for implant build names
func ProfileNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
return []string{}
}
results := []string{}
for _, profile := range pbProfiles.Profiles {
if strings.HasPrefix(profile.Name, prefix) {
results = append(results, profile.Name)
}
}
return results
}
+1 -1
View File
@@ -57,7 +57,7 @@ func printHelp(con *console.SliverConsoleClient) {
continue
}
} else {
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.MacroHelpGroup || key == consts.ExtensionHelpGroup {
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.AliasHelpGroup || key == consts.ExtensionHelpGroup {
continue
}
}
+5 -5
View File
@@ -71,7 +71,7 @@ var (
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.LoadMacroStr: loadMacrosHelp,
consts.AliasesStr: loadMacrosHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
@@ -211,12 +211,12 @@ profiles new --profile-name windows-shellcode --format shellcode --mtls 1.2.3.4
[[.Bold]][[.Underline]]++ Profiles ++[[.Normal]]
Profiles are an easy way to save an implant configuration and easily generate multiple copies of the binary with the same
settings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with generate-profile:
profiles new --profile-name mtls-profile --mtls foo.example.com --canary 1.foobar.com
generate-profile mtls-profile
settings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with "profiles generate":
profiles new --mtls foo.example.com --canary 1.foobar.com my-profile-name
profiles generate my-profile-name
`
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate-profile [name] <options>
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
+22 -15
View File
@@ -21,16 +21,15 @@ package jobs
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// JobsCmd - Manage server jobs (listeners, etc)
@@ -51,33 +50,41 @@ func JobsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
activeJobs[job.ID] = job
}
if 0 < len(activeJobs) {
printJobs(activeJobs)
PrintJobs(activeJobs, con)
} else {
con.PrintInfof("No active jobs\n")
}
}
}
func printJobs(jobs map[uint32]*clientpb.Job) {
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tName\tProtocol\tPort\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("Protocol")),
strings.Repeat("=", len("Port")))
// PrintJobs - Prints a list of active jobs
func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Name",
"Protocol",
"Port",
})
var keys []int
for _, job := range jobs {
keys = append(keys, int(job.ID))
}
sort.Ints(keys) // Fucking Go can't sort int32's, so we convert to/from int's
sort.Ints(keys)
for _, k := range keys {
job := jobs[uint32(k)]
fmt.Fprintf(table, "%d\t%s\t%s\t%d\t\n", job.ID, job.Name, job.Protocol, job.Port)
tw.AppendRow(table.Row{
fmt.Sprintf("%d", job.ID),
job.Name,
job.Protocol,
fmt.Sprintf("%d", job.Port),
})
}
table.Flush()
con.Printf("%s\n", tw.Render())
}
func jobKill(jobID uint32, con *console.SliverConsoleClient) {
-335
View File
@@ -1,335 +0,0 @@
package macros
/*
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"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
linuxDefaultHostProc = "/bin/bash"
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
)
var commandMap map[string]macro
var defaultHostProc = map[string]string{
"windows": windowsDefaultHostProc,
"linux": windowsDefaultHostProc,
"darwin": macosDefaultHostProc,
}
type binFiles struct {
Ext64Path string `json:"x64"`
Ext32Path string `json:"x86"`
}
type macroFile struct {
OS string `json:"os"`
Files binFiles `json:"files"`
}
type macroCommand struct {
Name string `json:"name"`
Entrypoint string `json:"entrypoint"`
Help string `json:"help"`
LongHelp string `json:"longHelp"`
AllowArgs bool `json:"allowArgs"`
DefaultArgs string `json:"defaultArgs"`
MacroFiles []macroFile `json:"extFiles"`
IsReflective bool `json:"isReflective"`
IsAssembly bool `json:"IsAssembly"`
}
func (ec *macroCommand) getDefaultProcess(targetOS string) (proc string, err error) {
proc, ok := defaultHostProc[targetOS]
if !ok {
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
}
return
}
type macro struct {
Name string `json:"macroName"`
Commands []macroCommand `json:"macroCommands"`
Path string
}
func (e *macro) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
for _, c := range e.Commands {
if cmdName == c.Name {
for _, ef := range c.MacroFiles {
if targetOS == ef.OS {
switch targetArch {
case "x86":
filePath = filepath.Join(e.Path, ef.Files.Ext32Path)
case "x64":
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
default:
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
}
}
}
}
}
if filePath == "" {
err = fmt.Errorf("no macro file found for %s/%s", targetOS, targetArch)
}
return
}
func (e *macro) getCommandFromName(name string) (extCmd *macroCommand, err error) {
for _, x := range e.Commands {
if x.Name == name {
extCmd = &x
return
}
}
err = fmt.Errorf("no macro command found for name %s", name)
return
}
// LoadMacroCmd - Locally load a macro into the Sliver shell.
func LoadMacroCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
// retrieve macro manifest
manifestPath := fmt.Sprintf("%s/%s", dirPath, "manifest.json")
jsonBytes, err := ioutil.ReadFile(manifestPath)
if err != nil {
con.PrintErrorf("%s\n", err)
}
// parse it
ext := &macro{}
err = json.Unmarshal(jsonBytes, ext)
if err != nil {
con.PrintErrorf("Error loading macro: %v", err)
return
}
ext.Path = dirPath
// for each macro command, add a new app command
for _, extCmd := range ext.Commands {
// do not add if the command already exists
if cmdExists(extCmd.Name, ctx.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
return
}
con.PrintInfof("Adding %s command: %s\n", extCmd.Name, extCmd.Help)
// Have to use a global map here, as passing the extCmd
// either by value or by ref fucks things up
commandMap[extCmd.Name] = *ext
helpMsg := fmt.Sprintf("[%s] %s", ext.Name, extCmd.Help)
macroCmd := &grumble.Command{
Name: extCmd.Name,
Help: helpMsg,
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
Run: func(extCtx *grumble.Context) error {
con.Println()
runMacroCommand(extCtx, con)
con.Println()
return nil
},
Flags: func(f *grumble.Flags) {
if extCmd.IsAssembly {
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
}
f.String("p", "process", "", "Path to process to host the shared object")
f.Bool("s", "save", false, "Save output to disk")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.StringList("arguments", "arguments", grumble.Default([]string{}))
},
HelpGroup: consts.MacroHelpGroup,
}
con.App.AddCommand(macroCmd)
}
con.PrintInfof("%s macro has been loaded\n", ext.Name)
}
func runMacroCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
ext, ok := commandMap[ctx.Command.Name]
if !ok {
con.PrintErrorf("No macro command found for `%s` command\n", ctx.Command.Name)
return
}
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
c, err := ext.getCommandFromName(ctx.Command.Name)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
args := ctx.Args.StringList("arguments")
var extArgs string
if len(c.DefaultArgs) != 0 && len(args) == 0 {
extArgs = c.DefaultArgs
} else {
extArgs = strings.Join(args, " ")
}
entryPoint := c.Entrypoint
processName := ctx.Flags.String("process")
if processName == "" {
processName, err = c.getDefaultProcess(session.GetOS())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
isDLL := false
if filepath.Ext(binPath) == ".dll" {
isDLL = true
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
var outFilePath *os.File
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
if c.IsAssembly {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: processName,
Arguments: extArgs,
Assembly: binData,
Arch: ctx.Flags.String("arch"),
Method: ctx.Flags.String("method"),
ClassName: ctx.Flags.String("class"),
AppDomain: ctx.Flags.String("app-domain"),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
if outFilePath != nil {
outFilePath.Write(executeAssemblyResp.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else if c.IsReflective {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
Request: con.ActiveTarget.Request(ctx),
Args: strings.Trim(extArgs, " "),
Data: binData,
ProcessName: processName,
EntryPoint: c.Entrypoint,
Kill: true,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(spawnDllResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
Request: con.ActiveTarget.Request(ctx),
Args: extArgs,
Data: binData,
EntryPoint: entryPoint,
ProcessName: processName,
Kill: true,
IsDLL: isDLL,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(sideloadResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
}
}
func cmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
}
}
return false
}
func init() {
commandMap = make(map[string]macro)
}
+23 -16
View File
@@ -19,19 +19,22 @@ package portfwd
*/
import (
"bytes"
"fmt"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// PortfwdCmd - Display information about tunneled port forward(s)
func PortfwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
PrintPortfwd(con)
}
// PrintPortfwd - Print the port forward(s)
func PrintPortfwd(con *console.SliverConsoleClient) {
portfwds := core.Portfwds.List()
if len(portfwds) == 0 {
con.PrintInfof("No port forwards\n")
@@ -40,18 +43,22 @@ func PortfwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
sort.Slice(portfwds[:], func(i, j int) bool {
return portfwds[i].ID < portfwds[j].ID
})
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tSession ID\tBind Address\tRemote Address\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Session ID")),
strings.Repeat("=", len("Bind Address")),
strings.Repeat("=", len("Remote Address")),
)
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Session ID",
"Bind Address",
"Remote Address",
})
for _, p := range portfwds {
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t\n", p.ID, p.SessionID, p.BindAddr, p.RemoteAddr)
tw.AppendHeader(table.Row{
p.ID,
p.SessionID,
p.BindAddr,
p.RemoteAddr,
})
}
table.Flush()
con.Printf("%s", outputBuf.String())
con.Printf("%s\n", tw.Render())
}
@@ -31,8 +31,8 @@ import (
"gopkg.in/AlecAivazis/survey.v1"
)
// SocksAddCmd - Add a new tunneled port forward
func SocksAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// SocksStartCmd - Add a new tunneled port forward
func SocksStartCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
@@ -27,8 +27,8 @@ import (
"github.com/desertbit/grumble"
)
// SocksRmCmd - Remove an existing tunneled port forward
func SocksRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// SocksStopCmd - Remove an existing tunneled port forward
func SocksStopCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
socksID := ctx.Flags.Int("id")
if socksID < 1 {
con.PrintErrorf("Must specify a valid socks5 id\n")
+3 -1
View File
@@ -174,7 +174,9 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
}
return nil, nil, nil
}
func Completer(con *console.SliverConsoleClient, prefix string, args []string) (results []string) {
// BeaconAndSessionIDCompleter - BeaconAndSessionIDCompleter for beacon / session ids
func BeaconAndSessionIDCompleter(prefix string, args []string, con *console.SliverConsoleClient) (results []string) {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return
@@ -62,7 +62,7 @@ func WebsitesAddContentCmd(ctx *grumble.Context, con *console.SliverConsoleClien
con.PrintErrorf("%s", err)
return
}
displayWebsite(web, con)
PrintWebsite(web, con)
}
func webAddDirectory(web *clientpb.WebsiteAddContent, webpath string, contentPath string) {
@@ -50,5 +50,5 @@ func WebsitesRmContent(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to remove content %s", err)
return
}
displayWebsite(web, con)
PrintWebsite(web, con)
}
@@ -39,5 +39,5 @@ func WebsitesUpdateContentCmd(ctx *grumble.Context, con *console.SliverConsoleCl
con.PrintErrorf("%s", err)
return
}
displayWebsite(web, con)
PrintWebsite(web, con)
}
+18 -12
View File
@@ -20,14 +20,14 @@ package websites
import (
"context"
"fmt"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/desertbit/grumble"
)
@@ -75,21 +75,23 @@ func ListWebsiteContent(websiteName string, con *console.SliverConsoleClient) {
return
}
if 0 < len(website.Contents) {
displayWebsite(website, con)
PrintWebsite(website, con)
} else {
con.PrintInfof("No content for '%s'", websiteName)
}
}
func displayWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
// PrintWebsite - Print a website and its contents, paths, etc.
func PrintWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
con.Println(console.Clearln + console.Info + web.Name)
con.Println()
table := tabwriter.NewWriter(con.App.Stdout(), 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Path\tContent-type\tSize\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t\n",
strings.Repeat("=", len("Path")),
strings.Repeat("=", len("Content-type")),
strings.Repeat("=", len("Size")))
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Path",
"Content-type",
"Size",
})
sortedContents := []*clientpb.WebContent{}
for _, content := range web.Contents {
sortedContents = append(sortedContents, content)
@@ -98,7 +100,11 @@ func displayWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
return sortedContents[i].Path < sortedContents[j].Path
})
for _, content := range sortedContents {
fmt.Fprintf(table, "%s\t%s\t%d\t\n", content.Path, content.ContentType, content.Size)
tw.AppendHeader(table.Row{
content.Path,
content.ContentType,
content.Size,
})
}
table.Flush()
con.Println(tw.Render())
}
+4 -4
View File
@@ -50,7 +50,7 @@ func WGPortFwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
pfwdAdd, err := con.Rpc.WGStartPortForward(context.Background(), &sliverpb.WGPortForwardStartReq{
portfwdAdd, err := con.Rpc.WGStartPortForward(context.Background(), &sliverpb.WGPortForwardStartReq{
LocalPort: int32(localPort),
RemoteAddress: remoteAddr,
Request: con.ActiveTarget.Request(ctx),
@@ -61,9 +61,9 @@ func WGPortFwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if pfwdAdd.Response != nil && pfwdAdd.Response.Err != "" {
con.PrintErrorf("Error: %s\n", pfwdAdd.Response.Err)
if portfwdAdd.Response != nil && portfwdAdd.Response.Err != "" {
con.PrintErrorf("Error: %s\n", portfwdAdd.Response.Err)
return
}
con.PrintInfof("Port forwarding %s -> %s:%s\n", pfwdAdd.Forwarder.LocalAddr, remoteHost, remotePort)
con.PrintInfof("Port forwarding %s -> %s:%s\n", portfwdAdd.Forwarder.LocalAddr, remoteHost, remotePort)
}
+16 -16
View File
@@ -19,15 +19,13 @@ package wireguard
*/
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// WGPortFwdListCmd - List WireGuard port forwards
@@ -44,12 +42,10 @@ func WGPortFwdListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
fwdList, err := con.Rpc.WGListForwarders(context.Background(), &sliverpb.WGTCPForwardersReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %v", err)
return
}
if fwdList.Response != nil && fwdList.Response.Err != "" {
con.PrintErrorf("Error: %s\n", fwdList.Response.Err)
return
@@ -59,18 +55,22 @@ func WGPortFwdListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if len(fwdList.Forwarders) == 0 {
con.PrintInfof("No port forwards\n")
} else {
outBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outBuf, 0, 3, 3, ' ', 0)
fmt.Fprintf(table, "ID\tLocal Address\tRemote Address\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Local Address")),
strings.Repeat("=", len("Remote Address")))
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Name",
"Protocol",
"Port",
})
for _, fwd := range fwdList.Forwarders {
fmt.Fprintf(table, "%d\t%s\t%s\t\n", fwd.ID, fwd.LocalAddr, fwd.RemoteAddr)
tw.AppendRow(table.Row{
fwd.ID,
fwd.LocalAddr,
fwd.RemoteAddr,
})
}
table.Flush()
con.Println(outBuf.String())
con.Println(tw.Render())
}
}
}
+14 -16
View File
@@ -19,15 +19,13 @@ package wireguard
*/
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// WGSocksListCmd - List WireGuard SOCKS proxies
@@ -44,30 +42,30 @@ func WGSocksListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
socksList, err := con.Rpc.WGListSocksServers(context.Background(), &sliverpb.WGSocksServersReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %v", err)
return
}
if socksList.Response != nil && socksList.Response.Err != "" {
con.PrintErrorf("Error: %s\n", socksList.Response.Err)
return
}
if socksList.Servers != nil {
if len(socksList.Servers) > 0 {
outBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outBuf, 0, 3, 3, ' ', 0)
fmt.Fprintf(table, "ID\tLocal Address\n")
fmt.Fprintf(table, "%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Local Address")))
if 0 < len(socksList.Servers) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Local Address",
})
for _, server := range socksList.Servers {
fmt.Fprintf(table, "%d\t%s\t\n", server.ID, server.LocalAddr)
tw.AppendRow(table.Row{
server.ID,
server.LocalAddr,
})
}
table.Flush()
con.Println(outBuf.String())
con.Println(tw.Render())
}
}
+23 -19
View File
@@ -97,22 +97,23 @@ const (
KickOperatorStr = "kick-operator"
MultiplayerModeStr = "multiplayer"
SessionsStr = "sessions"
BackgroundStr = "background"
InfoStr = "info"
UseStr = "use"
ReconfigStr = "reconfig"
PruneStr = "prune"
TasksStr = "tasks"
GenerateStr = "generate"
RegenerateStr = "regenerate"
CompilerStr = "info"
StagerStr = "stager"
ProfilesStr = "profiles"
BeaconStr = "beacon"
BeaconsStr = "beacons"
WatchStr = "watch"
SettingsStr = "settings"
SessionsStr = "sessions"
BackgroundStr = "background"
InfoStr = "info"
UseStr = "use"
ReconfigStr = "reconfig"
PruneStr = "prune"
TasksStr = "tasks"
GenerateStr = "generate"
RegenerateStr = "regenerate"
CompilerInfoStr = "info"
StagerStr = "stager"
ProfilesStr = "profiles"
BeaconStr = "beacon"
BeaconsStr = "beacons"
WatchStr = "watch"
SettingsStr = "settings"
SearchStr = "search"
// Generic
@@ -187,8 +188,11 @@ const (
MigrateStr = "migrate"
SideloadStr = "sideload"
SpawnDllStr = "spawndll"
ExtensionStr = "extension"
LoadMacroStr = "load-macro"
ExtensionsStr = "extensions"
InstallStr = "install"
ListStr = "list"
ArmoryStr = "armory"
AliasesStr = "aliases"
StageListenerStr = "stage-listener"
WebsitesStr = "websites"
@@ -237,6 +241,6 @@ const (
SliverHelpGroup = "Sliver:"
SliverWinHelpGroup = "Sliver - Windows:"
MultiplayerHelpGroup = "Multiplayer:"
MacroHelpGroup = "Sliver - 3rd Party macros:"
AliasHelpGroup = "Sliver - 3rd Party macros:"
ExtensionHelpGroup = "Sliver - 3rd Party extensions:"
)
+18
View File
@@ -1,5 +1,23 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"encoding/binary"
+5 -6
View File
@@ -22,6 +22,7 @@ import (
"context"
"fmt"
"io"
"log"
"net"
"sync"
"time"
@@ -30,7 +31,6 @@ import (
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/util/leaky"
)
@@ -43,7 +43,6 @@ var (
SocksConnPool = sync.Map{}
SocksProxyID = 0
)
var coreLog = log.NamedLogger("ClientCore", "sessions")
// PortfwdMeta - Metadata about a portfwd listener
type SocksProxyMeta struct {
@@ -121,7 +120,7 @@ func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
SocksConnPool.Delete(p.TunnelID)
continue
}
coreLog.Debugf("[socks] agent to Server To (Client to User) Data Sequence %d , Data Size %d \n", FromImplantSequence, len(p.Data))
log.Printf("[socks] agent to Server To (Client to User) Data Sequence %d , Data Size %d \n", FromImplantSequence, len(p.Data))
//fmt.Printf("recv data len %d \n", len(p.Data))
_, err := n.Write(p.Data)
if err != nil {
@@ -151,7 +150,7 @@ func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
Request: &commonpb.Request{SessionID: rpcSocks.SessionID},
})
}
coreLog.Infof("Socks Stop -> %s\n", tcpProxy.BindAddr)
log.Printf("Socks Stop -> %s\n", tcpProxy.BindAddr)
tcpProxy.Listener.Close()
proxy.CloseSend()
return nil
@@ -193,7 +192,7 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
SocksConnPool.Store(frame.TunnelID, conn)
coreLog.Infof("tcp conn %q<--><-->%q \n", conn.LocalAddr(), conn.RemoteAddr())
log.Printf("tcp conn %q<--><-->%q \n", conn.LocalAddr(), conn.RemoteAddr())
buff := leakyBuf.Get()
defer leakyBuf.Put(buff)
@@ -209,7 +208,7 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
if n > 0 {
frame.Data = buff[:n]
frame.Sequence = ToImplantSequence
coreLog.Debugf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
log.Printf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
err := stream.Send(frame)
if err != nil {
return
+3 -4
View File
@@ -135,8 +135,7 @@ rm -rf ./go
rm -f windows-go.zip go$GO_VER.windows-$GO_ARCH_1.zip
# --- Client ---
mkdir -p $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_1
mkdir -p $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_3
mkdir -p $CLIENT_OUTPUT_DIR/extensions/coff-loader/windows
echo "-----------------------------------------------------------------"
echo " Garble"
@@ -149,8 +148,8 @@ curl -L --output $OUTPUT_DIR/darwin/$GO_ARCH_2/garble https://github.com/moloch-
echo "-----------------------------------------------------------------"
echo " COFF Loader"
echo "-----------------------------------------------------------------"
curl -L --output $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_1/COFFLoader.x64.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x64.dll
curl -L --output $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_3/COFFLoader.x86.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x86.dll
curl -L --output $CLIENT_OUTPUT_DIR/extensions/coff-loader/windows/COFFLoader.x64.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x64.dll
curl -L --output $CLIENT_OUTPUT_DIR/extensions/coff-loader/windows/COFFLoader.x86.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x86.dll
# end
echo -e "clean up: $WORK_DIR"
+32 -15
View File
@@ -21,28 +21,45 @@ echo "----------------------------------------------------------------"
echo "WARNING: Running unit tests on slow systems can take a LONG time"
echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
echo "----------------------------------------------------------------"
TAGS=osusergo,netgo,cgosqlite,sqlite_omit_load_extension
## Client
# client / command / alias
if go test -tags=client,$TAGS ./client/command/alias ; then
:
else
exit 1
fi
# client / command / extensions
if go test -tags=client,$TAGS ./client/command/extensions ; then
:
else
exit 1
fi
## Util
# util
if go test -tags=server,gosqlite ./util ; then
if go test -tags=server,$TAGS ./util ; then
:
else
exit 1
fi
# util / encoders
if go test -tags=server,gosqlite ./util/encoders/basex ; then
if go test -tags=server,$TAGS ./util/encoders/basex ; then
:
else
exit 1
fi
if go test -tags=server,gosqlite ./util/encoders ; then
if go test -tags=server,$TAGS ./util/encoders ; then
:
else
exit 1
fi
if go test -tags=client,gosqlite ./util/encoders ; then
if go test -tags=client,$TAGS ./util/encoders ; then
:
else
exit 1
@@ -51,7 +68,7 @@ fi
## Implant
# implant / sliver / transports / dnsclient
if go test -tags=server,gosqlite ./implant/sliver/transports/dnsclient ; then
if go test -tags=server,$TAGS ./implant/sliver/transports/dnsclient ; then
:
else
exit 1
@@ -60,7 +77,7 @@ fi
## Server
# server / website
if go test -tags=server,gosqlite ./server/website ; then
if go test -tags=server,$TAGS ./server/website ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -68,7 +85,7 @@ else
fi
# server / loot
if go test -tags=server,gosqlite ./server/loot ; then
if go test -tags=server,$TAGS ./server/loot ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -76,7 +93,7 @@ else
fi
# server / certs
if go test -tags=server,gosqlite ./server/certs ; then
if go test -tags=server,$TAGS ./server/certs ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -84,7 +101,7 @@ else
fi
# server / cryptography
if go test -tags=server,gosqlite ./server/cryptography ; then
if go test -tags=server,$TAGS ./server/cryptography ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -92,7 +109,7 @@ else
fi
# server / cryptography / minisign
if go test -tags=server,gosqlite ./server/cryptography/minisign ; then
if go test -tags=server,$TAGS ./server/cryptography/minisign ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -100,7 +117,7 @@ else
fi
# server / gogo
if go test -tags=server,gosqlite ./server/gogo ; then
if go test -tags=server,$TAGS ./server/gogo ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -108,7 +125,7 @@ else
fi
# server / c2
if go test -tags=server,gosqlite ./server/c2 ; then
if go test -tags=server,$TAGS ./server/c2 ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -116,7 +133,7 @@ else
fi
# server / configs
if go test -tags=server,gosqlite ./server/configs ; then
if go test -tags=server,$TAGS ./server/configs ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -124,7 +141,7 @@ else
fi
# server / console
if go test -tags=server,gosqlite ./server/console ; then
if go test -tags=server,$TAGS ./server/console ; then
:
else
cat ~/.sliver/logs/sliver.log
@@ -132,7 +149,7 @@ else
fi
# server / generate
if go test -tags=server,gosqlite ./server/generate -timeout 6h ; then
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
:
else
cat ~/.sliver/logs/sliver.log
+2 -1
View File
@@ -5,6 +5,8 @@ go 1.17
// fix wgctrl requiring old wireguard
replace golang.zx2c4.com/wireguard => golang.zx2c4.com/wireguard v0.0.0-20210311162910-5f0c8b942d93
replace github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.4
require (
github.com/AlecAivazis/survey/v2 v2.2.2
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d
@@ -59,7 +61,6 @@ require (
)
require (
github.com/emirpasic/gods v1.12.0
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
github.com/shirou/gopsutil/v3 v3.21.10
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
+2 -4
View File
@@ -104,8 +104,6 @@ github.com/desertbit/columnize v2.1.0+incompatible h1:h55rYmdrWoTj7w9aAnCkxzM3C2
github.com/desertbit/columnize v2.1.0+incompatible/go.mod h1:5kPrzQwKbQ8E5D28nvTVPqIBJyj+8jvJzwt6HXZvXgI=
github.com/desertbit/go-shlex v0.1.1 h1:c65HnbgX1QyC6kPL1dMzUpZ4puNUE6ai/eVucWNLNsk=
github.com/desertbit/go-shlex v0.1.1/go.mod h1:Qbb+mJNud5AypgHZ81EL8syOGaWlwvAOTqS7XmWI4pQ=
github.com/desertbit/grumble v1.1.1 h1:1wxy6ka1aqbtA3kZIHaPfB/DD91HSM2m4Kx2QIIGfpE=
github.com/desertbit/grumble v1.1.1/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
github.com/desertbit/readline v1.5.1 h1:/wOIZkWYl1s+IvJm/5bOknfUgs6MhS9svRNZpFM53Os=
github.com/desertbit/readline v1.5.1/go.mod h1:pHQgTsCFs9Cpfh5mlSUFi9Xa5kkL4d8L1Jo4UVWzPw0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -114,8 +112,6 @@ github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -373,6 +369,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moloch--/grumble v1.1.4 h1:/utJ405bbS2/Kx73XE03zqP1a3ZE9p3vqD3ePvsrG8o=
github.com/moloch--/grumble v1.1.4/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945 h1:m3yCfV8Vqp4MF1B+gPPjbjINdufl0UXqyYplE0aGhx8=
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4HaTofvhFUZYtofeRbGXfczNwCie9MYoM4lP/o=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+1 -1
View File
@@ -18,7 +18,7 @@ package main
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// {{if or .Config.IsSharedLib .Config.IsShellcode}}
// {{if or .Config.IsSharedLib }}
//#include "sliver.h"
import "C"
+6 -1
View File
@@ -102,7 +102,12 @@ func Setup(force bool, echo bool) {
if force || localVer == "" || localVer != ver.GitCommit {
setupLog.Infof("Version mismatch %v != %v", localVer, ver.GitCommit)
if echo {
fmt.Printf("Unpacking assets ...\n")
fmt.Printf(`
Sliver Copyright (C) 2021 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
fmt.Printf("\n\nUnpacking assets ...\n")
}
setupGo(appDir)
setupCodenames(appDir)
+4
View File
@@ -32,6 +32,7 @@ import (
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/console"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/daemon"
"github.com/spf13/cobra"
@@ -125,6 +126,9 @@ var rootCmd = &cobra.Command{
assets.Setup(false, true)
certs.SetupCAs()
certs.SetupWGKeys()
cryptography.ECCServerKeyPair()
cryptography.TOTPServerSecret()
cryptography.MinisignServerPrivateKey()
serverConfig := configs.GetServerConfig()
c2.StartPersistentJobs(serverConfig)
+4
View File
@@ -10,6 +10,7 @@ import (
"github.com/bishopfox/sliver/server/c2"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/daemon"
"github.com/spf13/cobra"
)
@@ -50,6 +51,9 @@ var daemonCmd = &cobra.Command{
assets.Setup(force, false)
certs.SetupCAs()
certs.SetupWGKeys()
cryptography.ECCServerKeyPair()
cryptography.TOTPServerSecret()
cryptography.MinisignServerPrivateKey()
serverConfig := configs.GetServerConfig()
c2.StartPersistentJobs(serverConfig)
-10
View File
@@ -31,15 +31,12 @@ import (
consts "github.com/bishopfox/sliver/client/constants"
clienttransport "github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/transport"
"google.golang.org/grpc"
)
// Start - Starts the server console
func Start() {
generateServerSpecificValues()
_, ln, _ := transport.LocalListener()
ctxDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return ln.Dial()
@@ -60,13 +57,6 @@ func Start() {
clientconsole.Start(localRPC, command.BindCommands, serverOnlyCmds, true)
}
// generates server specific values if they do not already exist
func generateServerSpecificValues() {
cryptography.ECCServerKeyPair()
cryptography.TOTPServerSecret()
cryptography.MinisignServerPrivateKey()
}
// ServerOnlyCmds - Server only commands
func serverOnlyCmds(console *clientconsole.SliverConsoleClient) {
+11
View File
@@ -2,3 +2,14 @@ DB
===
[GORM-based](https://gorm.io/) database interactions.
#### Contents:
* `gosqlite/` - A fork of the GORM sqlite driver that uses a pure go sqlite3 implementation. This can be compiled using the Go build tag `gosqlite`
* `models/` - The database/GORM models
* `db.go` - Primary abstraction for client and db sessions
* `helpers.go` - Helper functions for querying the GORM models
* `logger.go` - Database logger
* `sql_cgo.go` - The CGO sqlite client
* `sql_go.go` - The pure Go sqlite client
* `sql.go` - Database setup and configuration
-16
View File
@@ -256,25 +256,9 @@ func GetSliversDir() string {
// SliverShellcode - Generates a sliver shellcode using sRDI
func SliverShellcode(name string, config *models.ImplantConfig) (string, error) {
// Compile go code
// Compile go code
var cc string
var cxx string
appDir := assets.GetRootAppDir()
// Don't use a cross-compiler if the target bin is built on the same platform
// as the sliver-server.
if runtime.GOOS != config.GOOS {
buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
if cc == "" {
return "", fmt.Errorf("CC '%s/%s' not found", config.GOOS, config.GOARCH)
}
}
goConfig := &gogo.GoConfig{
CGO: "0",
CC: cc,
CXX: cxx,
GOOS: config.GOOS,
GOARCH: config.GOARCH,
+8 -8
View File
@@ -25,7 +25,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/server/log"
@@ -64,19 +64,19 @@ type GoConfig struct {
// GetGoRootDir - Get the path to GOROOT
func GetGoRootDir(appDir string) string {
return path.Join(appDir, goDirName)
return filepath.Join(appDir, goDirName)
}
// GetGoCache - Get the OS temp dir (used for GOCACHE)
func GetGoCache(appDir string) string {
cachePath := path.Join(GetGoRootDir(appDir), "cache")
cachePath := filepath.Join(GetGoRootDir(appDir), "cache")
os.MkdirAll(cachePath, 0700)
return cachePath
}
// GetGoModCache - Get the GoMod cache dir
func GetGoModCache(appDir string) string {
cachePath := path.Join(GetGoRootDir(appDir), "modcache")
cachePath := filepath.Join(GetGoRootDir(appDir), "modcache")
os.MkdirAll(cachePath, 0700)
return cachePath
}
@@ -108,7 +108,7 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
if _, ok := ValidCompilerTargets(config)[target]; !ok {
return nil, fmt.Errorf(fmt.Sprintf("Invalid compiler target: %s", target))
}
garbleBinPath := path.Join(config.GOROOT, "bin", "garble")
garbleBinPath := filepath.Join(config.GOROOT, "bin", "garble")
garbleFlags := []string{fmt.Sprintf("-seed=%s", seed()), "-literals"}
garbleFlags = append(garbleFlags, garbleMaxLiteralSize()...)
command = append(garbleFlags, command...)
@@ -124,7 +124,7 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
fmt.Sprintf("GOMODCACHE=%s", config.GOMODCACHE),
fmt.Sprintf("GOPRIVATE=%s", config.GOPRIVATE),
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
fmt.Sprintf("PATH=%s:%s", path.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
fmt.Sprintf("PATH=%s:%s", filepath.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
}
var stdout bytes.Buffer
var stderr bytes.Buffer
@@ -148,7 +148,7 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
// GoCmd - Execute a go command
func GoCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
goBinPath := path.Join(config.GOROOT, "bin", "go")
goBinPath := filepath.Join(config.GOROOT, "bin", "go")
cmd := exec.Command(goBinPath, command...)
cmd.Dir = cwd
cmd.Env = []string{
@@ -160,7 +160,7 @@ func GoCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
fmt.Sprintf("GOCACHE=%s", config.GOCACHE),
fmt.Sprintf("GOMODCACHE=%s", config.GOMODCACHE),
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
fmt.Sprintf("PATH=%s:%s", path.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
fmt.Sprintf("PATH=%s:%s", filepath.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
}
var stdout bytes.Buffer
var stderr bytes.Buffer
+60
View File
@@ -19,9 +19,12 @@ package util
*/
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@@ -70,3 +73,60 @@ func ByteCountBinary(b int64) string {
}
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
}
// ReadFileFromTarGz - Read a file from a tar.gz file in-memory
func ReadFileFromTarGz(tarGzFile string, tarPath string) ([]byte, error) {
f, err := os.Open(tarGzFile)
if err != nil {
return nil, err
}
defer f.Close()
gzf, err := gzip.NewReader(f)
if err != nil {
return nil, err
}
defer gzf.Close()
tarReader := tar.NewReader(gzf)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if header.Name == tarPath {
switch header.Typeflag {
case tar.TypeDir: // = directory
continue
case tar.TypeReg: // = regular file
return ioutil.ReadAll(tarReader)
}
}
}
return nil, nil
}
// CopyFile - Copy a file from src to dst
func CopyFile(src string, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
err = out.Close()
if err != nil {
return err
}
return err
}
+10
View File
@@ -0,0 +1,10 @@
//go:build !windows
package util
import "path/filepath"
// ResolvePath - Resolve a path from an assumed root path
func ResolvePath(in string) string {
return filepath.Clean("/" + in)
}
+16
View File
@@ -0,0 +1,16 @@
//go:build !windows
package util
import "testing"
func TestResolvePath(t *testing.T) {
sample1 := "../../../../a/b/c"
if ResolvePath(sample1) != "/a/b/c" {
t.Fatalf("failed to resolve path correctly from: %s", sample1)
}
sample2 := "a/b/c/../../../.."
if ResolvePath(sample2) != "/" {
t.Fatalf("failed to resolve path correctly from: %s", sample2)
}
}
+20
View File
@@ -0,0 +1,20 @@
//go:build windows
package util
import (
"fmt"
"path/filepath"
"strings"
)
// ResolvePath - Resolve a path from an assumed root path, whilst dealing with
// Windows' stupid file system paths.
func ResolvePath(in string) string {
if strings.Contains(in, ":\\") {
parts := strings.Split(in, ":\\")
in = parts[len(parts)-1]
}
out := filepath.Clean(fmt.Sprintf("x:\\%s", in))
return strings.TrimPrefix(out, "x:")
}
+16
View File
@@ -39,6 +39,22 @@ func (c *Commands) Add(cmd *Command) {
c.list = append(c.list, cmd)
}
// Remove a command from the slice.
func (c *Commands) Remove(name string) {
var index int
var cmd *Command
found := false
for index, cmd = range c.list {
if cmd.Name == name {
found = true
break
}
}
if found {
c.list = append(c.list[:index], c.list[index+1:]...)
}
}
// All returns a slice of all commands.
func (c *Commands) All() []*Command {
return c.list
+8
View File
@@ -26,6 +26,7 @@ package grumble
import (
"fmt"
"runtime"
"sort"
"strconv"
"strings"
@@ -128,6 +129,13 @@ Loop:
break Loop
}
args = args[1:]
// A double dash (--) is used in most Bash commands to signify the end of command options,
// after which only positional arguments are accepted.
if a == "--" && runtime.GOOS == "linux" {
break Loop
}
pos := strings.Index(a, "=")
equalVal := ""
if pos > 0 {
-41
View File
@@ -1,41 +0,0 @@
Copyright (c) 2015, Emir Pasic
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
AVL Tree:
Copyright (c) 2017 Benjamin Scher Purcell <[email protected]>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-35
View File
@@ -1,35 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package containers provides core interfaces and functions for data structures.
//
// Container is the base interface for all data structures to implement.
//
// Iterators provide stateful iterators.
//
// Enumerable provides Ruby inspired (each, select, map, find, any?, etc.) container functions.
//
// Serialization provides serializers (marshalers) and deserializers (unmarshalers).
package containers
import "github.com/emirpasic/gods/utils"
// Container is base interface that all data structures implement.
type Container interface {
Empty() bool
Size() int
Clear()
Values() []interface{}
}
// GetSortedValues returns sorted container's elements with respect to the passed comparator.
// Does not effect the ordering of elements within the container.
func GetSortedValues(container Container, comparator utils.Comparator) []interface{} {
values := container.Values()
if len(values) < 2 {
return values
}
utils.Sort(values, comparator)
return values
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package containers
// EnumerableWithIndex provides functions for ordered containers whose values can be fetched by an index.
type EnumerableWithIndex interface {
// Each calls the given function once for each element, passing that element's index and value.
Each(func(index int, value interface{}))
// Map invokes the given function once for each element and returns a
// container containing the values returned by the given function.
// TODO would appreciate help on how to enforce this in containers (don't want to type assert when chaining)
// Map(func(index int, value interface{}) interface{}) Container
// Select returns a new container containing all elements for which the given function returns a true value.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Select(func(index int, value interface{}) bool) Container
// Any passes each element of the container to the given function and
// returns true if the function ever returns true for any element.
Any(func(index int, value interface{}) bool) bool
// All passes each element of the container to the given function and
// returns true if the function returns true for all elements.
All(func(index int, value interface{}) bool) bool
// Find passes each element of the container to the given function and returns
// the first (index,value) for which the function is true or -1,nil otherwise
// if no element matches the criteria.
Find(func(index int, value interface{}) bool) (int, interface{})
}
// EnumerableWithKey provides functions for ordered containers whose values whose elements are key/value pairs.
type EnumerableWithKey interface {
// Each calls the given function once for each element, passing that element's key and value.
Each(func(key interface{}, value interface{}))
// Map invokes the given function once for each element and returns a container
// containing the values returned by the given function as key/value pairs.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container
// Select returns a new container containing all elements for which the given function returns a true value.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Select(func(key interface{}, value interface{}) bool) Container
// Any passes each element of the container to the given function and
// returns true if the function ever returns true for any element.
Any(func(key interface{}, value interface{}) bool) bool
// All passes each element of the container to the given function and
// returns true if the function returns true for all elements.
All(func(key interface{}, value interface{}) bool) bool
// Find passes each element of the container to the given function and returns
// the first (key,value) for which the function is true or nil,nil otherwise if no element
// matches the criteria.
Find(func(key interface{}, value interface{}) bool) (interface{}, interface{})
}
-109
View File
@@ -1,109 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package containers
// IteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index.
type IteratorWithIndex interface {
// Next moves the iterator to the next element and returns true if there was a next element in the container.
// If Next() returns true, then next element's index and value can be retrieved by Index() and Value().
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
// Modifies the state of the iterator.
Next() bool
// Value returns the current element's value.
// Does not modify the state of the iterator.
Value() interface{}
// Index returns the current element's index.
// Does not modify the state of the iterator.
Index() int
// Begin resets the iterator to its initial state (one-before-first)
// Call Next() to fetch the first element if any.
Begin()
// First moves the iterator to the first element and returns true if there was a first element in the container.
// If First() returns true, then first element's index and value can be retrieved by Index() and Value().
// Modifies the state of the iterator.
First() bool
}
// IteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs.
type IteratorWithKey interface {
// Next moves the iterator to the next element and returns true if there was a next element in the container.
// If Next() returns true, then next element's key and value can be retrieved by Key() and Value().
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
// Modifies the state of the iterator.
Next() bool
// Value returns the current element's value.
// Does not modify the state of the iterator.
Value() interface{}
// Key returns the current element's key.
// Does not modify the state of the iterator.
Key() interface{}
// Begin resets the iterator to its initial state (one-before-first)
// Call Next() to fetch the first element if any.
Begin()
// First moves the iterator to the first element and returns true if there was a first element in the container.
// If First() returns true, then first element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator.
First() bool
}
// ReverseIteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index.
//
// Essentially it is the same as IteratorWithIndex, but provides additional:
//
// Prev() function to enable traversal in reverse
//
// Last() function to move the iterator to the last element.
//
// End() function to move the iterator past the last element (one-past-the-end).
type ReverseIteratorWithIndex interface {
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value().
// Modifies the state of the iterator.
Prev() bool
// End moves the iterator past the last element (one-past-the-end).
// Call Prev() to fetch the last element if any.
End()
// Last moves the iterator to the last element and returns true if there was a last element in the container.
// If Last() returns true, then last element's index and value can be retrieved by Index() and Value().
// Modifies the state of the iterator.
Last() bool
IteratorWithIndex
}
// ReverseIteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs.
//
// Essentially it is the same as IteratorWithKey, but provides additional:
//
// Prev() function to enable traversal in reverse
//
// Last() function to move the iterator to the last element.
type ReverseIteratorWithKey interface {
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator.
Prev() bool
// End moves the iterator past the last element (one-past-the-end).
// Call Prev() to fetch the last element if any.
End()
// Last moves the iterator to the last element and returns true if there was a last element in the container.
// If Last() returns true, then last element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator.
Last() bool
IteratorWithKey
}
-17
View File
@@ -1,17 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package containers
// JSONSerializer provides JSON serialization
type JSONSerializer interface {
// ToJSON outputs the JSON representation of containers's elements.
ToJSON() ([]byte, error)
}
// JSONDeserializer provides JSON deserialization
type JSONDeserializer interface {
// FromJSON populates containers's elements from the input JSON representation.
FromJSON([]byte) error
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package lists provides an abstract List interface.
//
// In computer science, a list or sequence is an abstract data type that represents an ordered sequence of values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream. Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item.
//
// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29
package lists
import (
"github.com/emirpasic/gods/containers"
"github.com/emirpasic/gods/utils"
)
// List interface that all lists implement
type List interface {
Get(index int) (interface{}, bool)
Remove(index int)
Add(values ...interface{})
Contains(values ...interface{}) bool
Sort(comparator utils.Comparator)
Swap(index1, index2 int)
Insert(index int, values ...interface{})
Set(index int, value interface{})
containers.Container
// Empty() bool
// Size() int
// Clear()
// Values() []interface{}
}
-79
View File
@@ -1,79 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package singlylinkedlist
import "github.com/emirpasic/gods/containers"
func assertEnumerableImplementation() {
var _ containers.EnumerableWithIndex = (*List)(nil)
}
// Each calls the given function once for each element, passing that element's index and value.
func (list *List) Each(f func(index int, value interface{})) {
iterator := list.Iterator()
for iterator.Next() {
f(iterator.Index(), iterator.Value())
}
}
// Map invokes the given function once for each element and returns a
// container containing the values returned by the given function.
func (list *List) Map(f func(index int, value interface{}) interface{}) *List {
newList := &List{}
iterator := list.Iterator()
for iterator.Next() {
newList.Add(f(iterator.Index(), iterator.Value()))
}
return newList
}
// Select returns a new container containing all elements for which the given function returns a true value.
func (list *List) Select(f func(index int, value interface{}) bool) *List {
newList := &List{}
iterator := list.Iterator()
for iterator.Next() {
if f(iterator.Index(), iterator.Value()) {
newList.Add(iterator.Value())
}
}
return newList
}
// Any passes each element of the container to the given function and
// returns true if the function ever returns true for any element.
func (list *List) Any(f func(index int, value interface{}) bool) bool {
iterator := list.Iterator()
for iterator.Next() {
if f(iterator.Index(), iterator.Value()) {
return true
}
}
return false
}
// All passes each element of the container to the given function and
// returns true if the function returns true for all elements.
func (list *List) All(f func(index int, value interface{}) bool) bool {
iterator := list.Iterator()
for iterator.Next() {
if !f(iterator.Index(), iterator.Value()) {
return false
}
}
return true
}
// Find passes each element of the container to the given function and returns
// the first (index,value) for which the function is true or -1,nil otherwise
// if no element matches the criteria.
func (list *List) Find(f func(index int, value interface{}) bool) (index int, value interface{}) {
iterator := list.Iterator()
for iterator.Next() {
if f(iterator.Index(), iterator.Value()) {
return iterator.Index(), iterator.Value()
}
}
return -1, nil
}
-70
View File
@@ -1,70 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package singlylinkedlist
import "github.com/emirpasic/gods/containers"
func assertIteratorImplementation() {
var _ containers.IteratorWithIndex = (*Iterator)(nil)
}
// Iterator holding the iterator's state
type Iterator struct {
list *List
index int
element *element
}
// Iterator returns a stateful iterator whose values can be fetched by an index.
func (list *List) Iterator() Iterator {
return Iterator{list: list, index: -1, element: nil}
}
// Next moves the iterator to the next element and returns true if there was a next element in the container.
// If Next() returns true, then next element's index and value can be retrieved by Index() and Value().
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
// Modifies the state of the iterator.
func (iterator *Iterator) Next() bool {
if iterator.index < iterator.list.size {
iterator.index++
}
if !iterator.list.withinRange(iterator.index) {
iterator.element = nil
return false
}
if iterator.index == 0 {
iterator.element = iterator.list.first
} else {
iterator.element = iterator.element.next
}
return true
}
// Value returns the current element's value.
// Does not modify the state of the iterator.
func (iterator *Iterator) Value() interface{} {
return iterator.element.value
}
// Index returns the current element's index.
// Does not modify the state of the iterator.
func (iterator *Iterator) Index() int {
return iterator.index
}
// Begin resets the iterator to its initial state (one-before-first)
// Call Next() to fetch the first element if any.
func (iterator *Iterator) Begin() {
iterator.index = -1
iterator.element = nil
}
// First moves the iterator to the first element and returns true if there was a first element in the container.
// If First() returns true, then first element's index and value can be retrieved by Index() and Value().
// Modifies the state of the iterator.
func (iterator *Iterator) First() bool {
iterator.Begin()
return iterator.Next()
}
@@ -1,31 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package singlylinkedlist
import (
"encoding/json"
"github.com/emirpasic/gods/containers"
)
func assertSerializationImplementation() {
var _ containers.JSONSerializer = (*List)(nil)
var _ containers.JSONDeserializer = (*List)(nil)
}
// ToJSON outputs the JSON representation of list's elements.
func (list *List) ToJSON() ([]byte, error) {
return json.Marshal(list.Values())
}
// FromJSON populates list's elements from the input JSON representation.
func (list *List) FromJSON(data []byte) error {
elements := []interface{}{}
err := json.Unmarshal(data, &elements)
if err == nil {
list.Clear()
list.Add(elements...)
}
return err
}
@@ -1,302 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singlylinkedlist implements the singly-linked list.
//
// Structure is not thread safe.
//
// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29
package singlylinkedlist
import (
"fmt"
"strings"
"github.com/emirpasic/gods/lists"
"github.com/emirpasic/gods/utils"
)
func assertListImplementation() {
var _ lists.List = (*List)(nil)
}
// List holds the elements, where each element points to the next element
type List struct {
first *element
last *element
size int
}
type element struct {
value interface{}
next *element
}
// New instantiates a new list and adds the passed values, if any, to the list
func New(values ...interface{}) *List {
list := &List{}
if len(values) > 0 {
list.Add(values...)
}
return list
}
// Add appends a value (one or more) at the end of the list (same as Append())
func (list *List) Add(values ...interface{}) {
for _, value := range values {
newElement := &element{value: value}
if list.size == 0 {
list.first = newElement
list.last = newElement
} else {
list.last.next = newElement
list.last = newElement
}
list.size++
}
}
// Append appends a value (one or more) at the end of the list (same as Add())
func (list *List) Append(values ...interface{}) {
list.Add(values...)
}
// Prepend prepends a values (or more)
func (list *List) Prepend(values ...interface{}) {
// in reverse to keep passed order i.e. ["c","d"] -> Prepend(["a","b"]) -> ["a","b","c",d"]
for v := len(values) - 1; v >= 0; v-- {
newElement := &element{value: values[v], next: list.first}
list.first = newElement
if list.size == 0 {
list.last = newElement
}
list.size++
}
}
// Get returns the element at index.
// Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false.
func (list *List) Get(index int) (interface{}, bool) {
if !list.withinRange(index) {
return nil, false
}
element := list.first
for e := 0; e != index; e, element = e+1, element.next {
}
return element.value, true
}
// Remove removes the element at the given index from the list.
func (list *List) Remove(index int) {
if !list.withinRange(index) {
return
}
if list.size == 1 {
list.Clear()
return
}
var beforeElement *element
element := list.first
for e := 0; e != index; e, element = e+1, element.next {
beforeElement = element
}
if element == list.first {
list.first = element.next
}
if element == list.last {
list.last = beforeElement
}
if beforeElement != nil {
beforeElement.next = element.next
}
element = nil
list.size--
}
// Contains checks if values (one or more) are present in the set.
// All values have to be present in the set for the method to return true.
// Performance time complexity of n^2.
// Returns true if no arguments are passed at all, i.e. set is always super-set of empty set.
func (list *List) Contains(values ...interface{}) bool {
if len(values) == 0 {
return true
}
if list.size == 0 {
return false
}
for _, value := range values {
found := false
for element := list.first; element != nil; element = element.next {
if element.value == value {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// Values returns all elements in the list.
func (list *List) Values() []interface{} {
values := make([]interface{}, list.size, list.size)
for e, element := 0, list.first; element != nil; e, element = e+1, element.next {
values[e] = element.value
}
return values
}
//IndexOf returns index of provided element
func (list *List) IndexOf(value interface{}) int {
if list.size == 0 {
return -1
}
for index, element := range list.Values() {
if element == value {
return index
}
}
return -1
}
// Empty returns true if list does not contain any elements.
func (list *List) Empty() bool {
return list.size == 0
}
// Size returns number of elements within the list.
func (list *List) Size() int {
return list.size
}
// Clear removes all elements from the list.
func (list *List) Clear() {
list.size = 0
list.first = nil
list.last = nil
}
// Sort sort values (in-place) using.
func (list *List) Sort(comparator utils.Comparator) {
if list.size < 2 {
return
}
values := list.Values()
utils.Sort(values, comparator)
list.Clear()
list.Add(values...)
}
// Swap swaps values of two elements at the given indices.
func (list *List) Swap(i, j int) {
if list.withinRange(i) && list.withinRange(j) && i != j {
var element1, element2 *element
for e, currentElement := 0, list.first; element1 == nil || element2 == nil; e, currentElement = e+1, currentElement.next {
switch e {
case i:
element1 = currentElement
case j:
element2 = currentElement
}
}
element1.value, element2.value = element2.value, element1.value
}
}
// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right.
// Does not do anything if position is negative or bigger than list's size
// Note: position equal to list's size is valid, i.e. append.
func (list *List) Insert(index int, values ...interface{}) {
if !list.withinRange(index) {
// Append
if index == list.size {
list.Add(values...)
}
return
}
list.size += len(values)
var beforeElement *element
foundElement := list.first
for e := 0; e != index; e, foundElement = e+1, foundElement.next {
beforeElement = foundElement
}
if foundElement == list.first {
oldNextElement := list.first
for i, value := range values {
newElement := &element{value: value}
if i == 0 {
list.first = newElement
} else {
beforeElement.next = newElement
}
beforeElement = newElement
}
beforeElement.next = oldNextElement
} else {
oldNextElement := beforeElement.next
for _, value := range values {
newElement := &element{value: value}
beforeElement.next = newElement
beforeElement = newElement
}
beforeElement.next = oldNextElement
}
}
// Set value at specified index
// Does not do anything if position is negative or bigger than list's size
// Note: position equal to list's size is valid, i.e. append.
func (list *List) Set(index int, value interface{}) {
if !list.withinRange(index) {
// Append
if index == list.size {
list.Add(value)
}
return
}
foundElement := list.first
for e := 0; e != index; {
e, foundElement = e+1, foundElement.next
}
foundElement.value = value
}
// String returns a string representation of container
func (list *List) String() string {
str := "SinglyLinkedList\n"
values := []string{}
for element := list.first; element != nil; element = element.next {
values = append(values, fmt.Sprintf("%v", element.value))
}
str += strings.Join(values, ", ")
return str
}
// Check that the index is within bounds of the list
func (list *List) withinRange(index int) bool {
return index >= 0 && index < list.size
}
-251
View File
@@ -1,251 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utils
import "time"
// Comparator will make type assertion (see IntComparator for example),
// which will panic if a or b are not of the asserted type.
//
// Should return a number:
// negative , if a < b
// zero , if a == b
// positive , if a > b
type Comparator func(a, b interface{}) int
// StringComparator provides a fast comparison on strings
func StringComparator(a, b interface{}) int {
s1 := a.(string)
s2 := b.(string)
min := len(s2)
if len(s1) < len(s2) {
min = len(s1)
}
diff := 0
for i := 0; i < min && diff == 0; i++ {
diff = int(s1[i]) - int(s2[i])
}
if diff == 0 {
diff = len(s1) - len(s2)
}
if diff < 0 {
return -1
}
if diff > 0 {
return 1
}
return 0
}
// IntComparator provides a basic comparison on int
func IntComparator(a, b interface{}) int {
aAsserted := a.(int)
bAsserted := b.(int)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Int8Comparator provides a basic comparison on int8
func Int8Comparator(a, b interface{}) int {
aAsserted := a.(int8)
bAsserted := b.(int8)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Int16Comparator provides a basic comparison on int16
func Int16Comparator(a, b interface{}) int {
aAsserted := a.(int16)
bAsserted := b.(int16)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Int32Comparator provides a basic comparison on int32
func Int32Comparator(a, b interface{}) int {
aAsserted := a.(int32)
bAsserted := b.(int32)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Int64Comparator provides a basic comparison on int64
func Int64Comparator(a, b interface{}) int {
aAsserted := a.(int64)
bAsserted := b.(int64)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// UIntComparator provides a basic comparison on uint
func UIntComparator(a, b interface{}) int {
aAsserted := a.(uint)
bAsserted := b.(uint)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// UInt8Comparator provides a basic comparison on uint8
func UInt8Comparator(a, b interface{}) int {
aAsserted := a.(uint8)
bAsserted := b.(uint8)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// UInt16Comparator provides a basic comparison on uint16
func UInt16Comparator(a, b interface{}) int {
aAsserted := a.(uint16)
bAsserted := b.(uint16)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// UInt32Comparator provides a basic comparison on uint32
func UInt32Comparator(a, b interface{}) int {
aAsserted := a.(uint32)
bAsserted := b.(uint32)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// UInt64Comparator provides a basic comparison on uint64
func UInt64Comparator(a, b interface{}) int {
aAsserted := a.(uint64)
bAsserted := b.(uint64)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Float32Comparator provides a basic comparison on float32
func Float32Comparator(a, b interface{}) int {
aAsserted := a.(float32)
bAsserted := b.(float32)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// Float64Comparator provides a basic comparison on float64
func Float64Comparator(a, b interface{}) int {
aAsserted := a.(float64)
bAsserted := b.(float64)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// ByteComparator provides a basic comparison on byte
func ByteComparator(a, b interface{}) int {
aAsserted := a.(byte)
bAsserted := b.(byte)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// RuneComparator provides a basic comparison on rune
func RuneComparator(a, b interface{}) int {
aAsserted := a.(rune)
bAsserted := b.(rune)
switch {
case aAsserted > bAsserted:
return 1
case aAsserted < bAsserted:
return -1
default:
return 0
}
}
// TimeComparator provides a basic comparison on time.Time
func TimeComparator(a, b interface{}) int {
aAsserted := a.(time.Time)
bAsserted := b.(time.Time)
switch {
case aAsserted.After(bAsserted):
return 1
case aAsserted.Before(bAsserted):
return -1
default:
return 0
}
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utils
import "sort"
// Sort sorts values (in-place) with respect to the given comparator.
//
// Uses Go's sort (hybrid of quicksort for large and then insertion sort for smaller slices).
func Sort(values []interface{}, comparator Comparator) {
sort.Sort(sortable{values, comparator})
}
type sortable struct {
values []interface{}
comparator Comparator
}
func (s sortable) Len() int {
return len(s.values)
}
func (s sortable) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
}
func (s sortable) Less(i, j int) bool {
return s.comparator(s.values[i], s.values[j]) < 0
}
-47
View File
@@ -1,47 +0,0 @@
// Copyright (c) 2015, Emir Pasic. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package utils provides common utility functions.
//
// Provided functionalities:
// - sorting
// - comparators
package utils
import (
"fmt"
"strconv"
)
// ToString converts a value to string.
func ToString(value interface{}) string {
switch value.(type) {
case string:
return value.(string)
case int8:
return strconv.FormatInt(int64(value.(int8)), 10)
case int16:
return strconv.FormatInt(int64(value.(int16)), 10)
case int32:
return strconv.FormatInt(int64(value.(int32)), 10)
case int64:
return strconv.FormatInt(int64(value.(int64)), 10)
case uint8:
return strconv.FormatUint(uint64(value.(uint8)), 10)
case uint16:
return strconv.FormatUint(uint64(value.(uint16)), 10)
case uint32:
return strconv.FormatUint(uint64(value.(uint32)), 10)
case uint64:
return strconv.FormatUint(uint64(value.(uint64)), 10)
case float32:
return strconv.FormatFloat(float64(value.(float32)), 'g', -1, 64)
case float64:
return strconv.FormatFloat(float64(value.(float64)), 'g', -1, 64)
case bool:
return strconv.FormatBool(value.(bool))
default:
return fmt.Sprintf("%+v", value)
}
}
+1 -7
View File
@@ -107,7 +107,7 @@ github.com/desertbit/columnize
# github.com/desertbit/go-shlex v0.1.1
## explicit; go 1.14
github.com/desertbit/go-shlex
# github.com/desertbit/grumble v1.1.1
# github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.4
## explicit; go 1.12
github.com/desertbit/grumble
# github.com/desertbit/readline v1.5.1
@@ -117,12 +117,6 @@ github.com/desertbit/readline
## explicit
github.com/dlclark/regexp2
github.com/dlclark/regexp2/syntax
# github.com/emirpasic/gods v1.12.0
## explicit
github.com/emirpasic/gods/containers
github.com/emirpasic/gods/lists
github.com/emirpasic/gods/lists/singlylinkedlist
github.com/emirpasic/gods/utils
# github.com/fatih/color v1.12.0
## explicit; go 1.13
github.com/fatih/color