mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63c316e4d7 | ||
|
|
cf8e6ace2e | ||
|
|
131061a015 | ||
|
|
ed18705ccf | ||
|
|
88be0e0846 | ||
|
|
edd330a4e1 | ||
|
|
26c4d04d8d | ||
|
|
283f605351 | ||
|
|
fd82c4b171 | ||
|
|
3d0d61685c | ||
|
|
177358040f | ||
|
|
e4db7d3323 | ||
|
|
7b42983e4f | ||
|
|
441f97ad6f | ||
|
|
efea316519 | ||
|
|
f391f72b04 | ||
|
|
2ebaf3115c | ||
|
|
630adc0e48 | ||
|
|
981aa8b80f | ||
|
|
024f70edc5 | ||
|
|
bf9b3f4aa6 | ||
|
|
5fc94d24bd | ||
|
|
5394f77f4a | ||
|
|
b41ac3a229 | ||
|
|
5c3fd8cb2b | ||
|
|
adbe7c7def | ||
|
|
bfcbb0bba6 | ||
|
|
4495c36853 | ||
|
|
f8d2f30344 | ||
|
|
55752d1109 | ||
|
|
48f7560655 | ||
|
|
92e0f87d44 | ||
|
|
13498da60c | ||
|
|
9c878527a5 | ||
|
|
f81419614d | ||
|
|
368442e094 | ||
|
|
670c4b7f9a | ||
|
|
77693c6be1 | ||
|
|
35bd3fd44a | ||
|
|
a916f8b38b | ||
|
|
fa2645e186 | ||
|
|
597950fa0a | ||
|
|
2acced66f1 | ||
|
|
39a6e13ae4 |
No files matched your search
@@ -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 +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/**
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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` -
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package armory
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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 := ¯o{}
|
||||
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)
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:"
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package httpclient
|
||||
|
||||
/*
|
||||
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 (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
// GetHTTPDriver - Get an instance of the specified HTTP driver
|
||||
func GetHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
|
||||
switch opts.Driver {
|
||||
|
||||
case goHTTPDriver:
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("WARNING: unknown HTTP driver: %s", opts.Driver)
|
||||
// {{end}}
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
/*
|
||||
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 (
|
||||
"net/url"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/httpclient/winhttp"
|
||||
)
|
||||
|
||||
// GetHTTPDriver - Get an instance of the specified HTTP driver
|
||||
func GetHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
|
||||
switch opts.Driver {
|
||||
|
||||
case goHTTPDriver:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Using go http driver")
|
||||
// {{end}}
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
|
||||
case winHTTPDriver:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Using winhttp driver")
|
||||
// {{end}}
|
||||
return WinHTTPDriver(origin, secure, opts)
|
||||
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("WARNING: unknown HTTP driver: %s", opts.Driver)
|
||||
// {{end}}
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// WinHTTPDriver - Initialize a WinHTTP driver (Windows only)
|
||||
func WinHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
|
||||
port := uint16(80)
|
||||
if secure {
|
||||
port = uint16(80)
|
||||
}
|
||||
c2URL, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driver := winhttp.NewWinHTTPClient(c2URL.Host, port)
|
||||
return driver, nil
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
/*
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/proxy"
|
||||
)
|
||||
|
||||
// GoHTTPDriver - Pure Go HTTP driver
|
||||
func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
|
||||
var transport *http.Transport
|
||||
if !secure {
|
||||
transport = &http.Transport{
|
||||
Dial: proxy.Direct.Dial,
|
||||
TLSHandshakeTimeout: opts.TlsTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
}
|
||||
} else {
|
||||
transport = &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: opts.NetTimeout,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: opts.TlsTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
}
|
||||
}
|
||||
client := &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: opts.NetTimeout,
|
||||
Transport: transport,
|
||||
}
|
||||
parseProxyConfig(origin, transport, opts.ProxyConfig)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func parseProxyConfig(origin string, transport *http.Transport, proxyConfig string) {
|
||||
switch proxyConfig {
|
||||
case "never":
|
||||
break
|
||||
case "":
|
||||
fallthrough
|
||||
case "auto":
|
||||
p := proxy.NewProvider("").GetHTTPSProxy(origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Found proxy %#v\n", p)
|
||||
// {{end}}
|
||||
proxyURL := p.URL()
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Force proxy %#v\n", proxyConfig)
|
||||
// {{end}}
|
||||
proxyURL, err := url.Parse(proxyConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
}
|
||||
|
||||
// Jar - CookieJar implementation that ignores domains/origins
|
||||
type Jar struct {
|
||||
lk sync.Mutex
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func cookieJar() *Jar {
|
||||
return &Jar{
|
||||
lk: sync.Mutex{},
|
||||
cookies: []*http.Cookie{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewJar - Get a new instance of a cookie jar
|
||||
func NewJar() *Jar {
|
||||
jar := new(Jar)
|
||||
jar.cookies = make([]*http.Cookie, 0)
|
||||
return jar
|
||||
}
|
||||
|
||||
// SetCookies handles the receipt of the cookies in a reply for the
|
||||
// given URL (which is ignored).
|
||||
func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
jar.lk.Lock()
|
||||
jar.cookies = append(jar.cookies, cookies...)
|
||||
jar.lk.Unlock()
|
||||
}
|
||||
|
||||
// Cookies returns the cookies to send in a request for the given URL.
|
||||
// It is up to the implementation to honor the standard cookie use
|
||||
// restrictions such as in RFC 6265 (which we do not).
|
||||
func (jar *Jar) Cookies(u *url.URL) []*http.Cookie {
|
||||
return jar.cookies
|
||||
}
|
||||
@@ -23,11 +23,13 @@ package httpclient
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -42,14 +44,12 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
"github.com/bishopfox/sliver/implant/sliver/proxy"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
goHTTPDriver = "go"
|
||||
winHTTPDriver = "winhttp"
|
||||
|
||||
userAgent = "{{GenerateUserAgent}}"
|
||||
nonceQueryArgs = "abcdefghijklmnopqrstuvwxyz_"
|
||||
acceptHeaderValue = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||
@@ -62,7 +62,6 @@ var (
|
||||
|
||||
// HTTPOptions - c2 specific configuration options
|
||||
type HTTPOptions struct {
|
||||
Driver string
|
||||
NetTimeout time.Duration
|
||||
TlsTimeout time.Duration
|
||||
PollTimeout time.Duration
|
||||
@@ -91,13 +90,7 @@ func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
|
||||
if err != nil || maxErrors < 0 {
|
||||
maxErrors = 10
|
||||
}
|
||||
driverName := strings.TrimSpace(strings.ToLower(c2URI.Query().Get("driver")))
|
||||
if driverName == "" {
|
||||
driverName = goHTTPDriver
|
||||
}
|
||||
|
||||
return &HTTPOptions{
|
||||
Driver: driverName,
|
||||
NetTimeout: netTimeout,
|
||||
TlsTimeout: tlsTimeout,
|
||||
PollTimeout: pollTimeout,
|
||||
@@ -112,7 +105,7 @@ func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
|
||||
// HTTPStartSession - Attempts to start a session with a given address
|
||||
func HTTPStartSession(address string, pathPrefix string, opts *HTTPOptions) (*SliverHTTPClient, error) {
|
||||
var client *SliverHTTPClient
|
||||
client = httpsClient(address, opts)
|
||||
client = httpsClient(address, opts.NetTimeout, opts.TlsTimeout, opts.ProxyConfig)
|
||||
client.pollTimeout = opts.PollTimeout
|
||||
client.PathPrefix = pathPrefix
|
||||
err := client.SessionInit()
|
||||
@@ -121,7 +114,7 @@ func HTTPStartSession(address string, pathPrefix string, opts *HTTPOptions) (*Sl
|
||||
if strings.HasSuffix(address, ":443") {
|
||||
address = fmt.Sprintf("%s:80", address[:len(address)-4])
|
||||
}
|
||||
client = httpClient(address, opts) // Fallback to insecure HTTP
|
||||
client = httpClient(address, opts.NetTimeout, opts.TlsTimeout, opts.ProxyConfig) // Fallback to insecure HTTP
|
||||
err = client.SessionInit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -130,16 +123,11 @@ func HTTPStartSession(address string, pathPrefix string, opts *HTTPOptions) (*Sl
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// HTTPDriver - The interface to send/recv HTTP data
|
||||
type HTTPDriver interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// SliverHTTPClient - Helper struct to keep everything together
|
||||
type SliverHTTPClient struct {
|
||||
Origin string
|
||||
PathPrefix string
|
||||
driver HTTPDriver
|
||||
Client *http.Client
|
||||
ProxyURL string
|
||||
SessionCtx *cryptography.CipherContext
|
||||
SessionID string
|
||||
@@ -235,7 +223,7 @@ func (s *SliverHTTPClient) DoPoll(req *http.Request) (*http.Response, []byte, er
|
||||
var data []byte
|
||||
var err error
|
||||
go func() {
|
||||
resp, err = s.driver.Do(req.WithContext(ctx))
|
||||
resp, err = s.Client.Do(req.WithContext(ctx))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
done <- ctx.Err()
|
||||
@@ -276,7 +264,7 @@ func (s *SliverHTTPClient) establishSessionID(sessionInit []byte) error {
|
||||
log.Printf("[http] POST -> %s (%d bytes)", uri, len(sessionInit))
|
||||
// {{end}}
|
||||
|
||||
resp, err := s.driver.Do(req)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] http response error: %s", err)
|
||||
@@ -411,7 +399,7 @@ func (s *SliverHTTPClient) WriteEnvelope(envelope *pb.Envelope) error {
|
||||
// {{end}}
|
||||
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, reader)
|
||||
resp, err := s.driver.Do(req)
|
||||
resp, err := s.Client.Do(req)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] POST request completed")
|
||||
// {{end}}
|
||||
@@ -455,7 +443,7 @@ func (s *SliverHTTPClient) CloseSession() error {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET -> %s", uri)
|
||||
// {{end}}
|
||||
resp, err := s.driver.Do(req)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET failed %v", err)
|
||||
@@ -565,40 +553,122 @@ func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext
|
||||
|
||||
// [ HTTP(S) Clients ] ------------------------------------------------------------
|
||||
|
||||
func httpClient(address string, opts *HTTPOptions) *SliverHTTPClient {
|
||||
origin := fmt.Sprintf("http://%s", address)
|
||||
driver, err := GetHTTPDriver(origin, false, opts)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] failed to initialize driver: %v", err)
|
||||
// {{end}}
|
||||
return nil
|
||||
func httpClient(address string, netTimeout time.Duration, tlsTimeout time.Duration, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
Dial: proxy.Direct.Dial,
|
||||
TLSHandshakeTimeout: tlsTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
}
|
||||
client := &SliverHTTPClient{
|
||||
Origin: origin,
|
||||
driver: driver,
|
||||
Origin: fmt.Sprintf("http://%s", address),
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: netTimeout,
|
||||
Transport: transport,
|
||||
},
|
||||
pollMutex: &sync.Mutex{},
|
||||
Closed: false,
|
||||
}
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func httpsClient(address string, opts *HTTPOptions) *SliverHTTPClient {
|
||||
origin := fmt.Sprintf("https://%s", address)
|
||||
driver, err := GetHTTPDriver(origin, true, opts)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] failed to initialize driver: %v", err)
|
||||
// {{end}}
|
||||
return nil
|
||||
func httpsClient(address string, netTimeout time.Duration, tlsTimeout time.Duration, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: netTimeout,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: tlsTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
}
|
||||
client := &SliverHTTPClient{
|
||||
Origin: origin,
|
||||
driver: driver,
|
||||
Origin: fmt.Sprintf("https://%s", address),
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: netTimeout,
|
||||
Transport: transport,
|
||||
},
|
||||
pollMutex: &sync.Mutex{},
|
||||
Closed: false,
|
||||
}
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func parseProxyConfig(client *SliverHTTPClient, transport *http.Transport, proxyConfig string) {
|
||||
switch proxyConfig {
|
||||
case "never":
|
||||
break
|
||||
case "":
|
||||
fallthrough
|
||||
case "auto":
|
||||
p := proxy.NewProvider("").GetHTTPSProxy(client.Origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Found proxy %#v\n", p)
|
||||
// {{end}}
|
||||
proxyURL := p.URL()
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Force proxy %#v\n", proxyConfig)
|
||||
// {{end}}
|
||||
proxyURL, err := url.Parse(proxyConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Jar - CookieJar implementation that ignores domains/origins
|
||||
type Jar struct {
|
||||
lk sync.Mutex
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func cookieJar() *Jar {
|
||||
return &Jar{
|
||||
lk: sync.Mutex{},
|
||||
cookies: []*http.Cookie{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewJar - Get a new instance of a cookie jar
|
||||
func NewJar() *Jar {
|
||||
jar := new(Jar)
|
||||
jar.cookies = make([]*http.Cookie, 0)
|
||||
return jar
|
||||
}
|
||||
|
||||
// SetCookies handles the receipt of the cookies in a reply for the
|
||||
// given URL (which is ignored).
|
||||
func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
jar.lk.Lock()
|
||||
jar.cookies = append(jar.cookies, cookies...)
|
||||
jar.lk.Unlock()
|
||||
}
|
||||
|
||||
// Cookies returns the cookies to send in a request for the given URL.
|
||||
// It is up to the implementation to honor the standard cookie use
|
||||
// restrictions such as in RFC 6265 (which we do not).
|
||||
func (jar *Jar) Cookies(u *url.URL) []*http.Cookie {
|
||||
return jar.cookies
|
||||
}
|
||||
|
||||
// {{end}} -HTTPc2Enabled
|
||||
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -1,222 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package winhttp
|
||||
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
// NewWinHTTPClient - Init a new WinHTTPClient
|
||||
func NewWinHTTPClient(hostname string, port uint16) *WinHTTPClient {
|
||||
return &WinHTTPClient{
|
||||
hostname: hostname,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// WinHTTPClient - A WinHTTP driver
|
||||
type WinHTTPClient struct {
|
||||
port uint16
|
||||
hostname string
|
||||
|
||||
hSession HInternet
|
||||
|
||||
referrer string
|
||||
acceptTypes []string
|
||||
}
|
||||
|
||||
// Do - Execute an HTTP request
|
||||
func (w *WinHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
var err error
|
||||
if w.hSession == 0 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] starting new session ...")
|
||||
// {{end}}
|
||||
w.hSession, err = Open(
|
||||
req.Header.Get("User-Agent"),
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] open failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
hConnect, err := Connect(w.hSession, w.hostname, int(w.port))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] connect failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
openReqFlags := uint32(0)
|
||||
reqPath := fmt.Sprintf("%s?%s", req.URL.Path, req.URL.RawQuery)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] req -> %s", reqPath)
|
||||
// {{end}}
|
||||
hRequest, err := OpenRequest(hConnect, req.Method, reqPath, "", w.referrer, w.acceptTypes, openReqFlags)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] open request failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dwFlags := uint32(SECURITY_FLAG_IGNORE_UNKNOWN_CA)
|
||||
err = SetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, uintptr(unsafe.Pointer(&dwFlags)), unsafe.Sizeof(dwFlags))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] set option failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sendReqContext := uint32(0)
|
||||
if req.Body != nil {
|
||||
var body []byte
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] io read body failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
err = SendRequest(hRequest, "", &body[0], len(body), len(body), &sendReqContext)
|
||||
} else {
|
||||
err = SendRequest(hRequest, "", nil, 0, 0, &sendReqContext)
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] send request failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ReceiveResponse(hRequest)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] recv response failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utf16StatusCode := make([]uint16, 16)
|
||||
statusCodeSize := uint32(unsafe.Sizeof(uint16(0)) * 16)
|
||||
err = QueryHeaders(
|
||||
hRequest,
|
||||
WINHTTP_QUERY_STATUS_CODE,
|
||||
"",
|
||||
&utf16StatusCode[0],
|
||||
&statusCodeSize,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] query headers failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
statusCode, err := strconv.Atoi(syscall.UTF16ToString(utf16StatusCode[:]))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] failed to parse utf16 status code: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utf16Status := make([]uint16, 16)
|
||||
statusSize := uint32(unsafe.Sizeof(uint16(0)) * 16)
|
||||
err = QueryHeaders(
|
||||
hRequest,
|
||||
WINHTTP_QUERY_STATUS_TEXT,
|
||||
"",
|
||||
&utf16Status[0],
|
||||
&statusSize,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] query headers failed: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
status := syscall.UTF16ToString(utf16Status[:])
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] status code: %d", statusCode)
|
||||
// {{end}}
|
||||
|
||||
var dataSize uint
|
||||
dataSize, err = QueryDataAvailable(hRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] data size: %v", dataSize)
|
||||
// {{end}}
|
||||
dataBuf := make([]byte, dataSize)
|
||||
var bytesRead int
|
||||
bytesRead, err = ReadData(
|
||||
hRequest,
|
||||
dataBuf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[winhttp] bytesRead: %v", bytesRead)
|
||||
log.Printf("[winhttp] data: %v\n", string(dataBuf))
|
||||
// {{else}}
|
||||
bytesRead++
|
||||
bytesRead--
|
||||
// {{end}}
|
||||
|
||||
resp := &http.Response{
|
||||
Status: status,
|
||||
StatusCode: statusCode,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Body: ioutil.NopCloser(bytes.NewReader(dataBuf)),
|
||||
ContentLength: int64(dataSize),
|
||||
Request: req,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package winhttp
|
||||
|
||||
// Implements flags necessary for working with WinHTTP.
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
/* WinHTTP error codes */
|
||||
WINHTTP_ERROR_BASE = 12000
|
||||
ERROR_WINHTTP_OUT_OF_HANDLES = syscall.Errno(WINHTTP_ERROR_BASE + 1)
|
||||
ERROR_WINHTTP_TIMEOUT = syscall.Errno(WINHTTP_ERROR_BASE + 2)
|
||||
ERROR_WINHTTP_INTERNAL_ERROR = syscall.Errno(WINHTTP_ERROR_BASE + 4)
|
||||
ERROR_WINHTTP_INVALID_URL = syscall.Errno(WINHTTP_ERROR_BASE + 5)
|
||||
ERROR_WINHTTP_UNRECOGNIZED_SCHEME = syscall.Errno(WINHTTP_ERROR_BASE + 6)
|
||||
ERROR_WINHTTP_NAME_NOT_RESOLVED = syscall.Errno(WINHTTP_ERROR_BASE + 7)
|
||||
ERROR_WINHTTP_INVALID_OPTION = syscall.Errno(WINHTTP_ERROR_BASE + 9)
|
||||
ERROR_WINHTTP_OPTION_NOT_SETTABLE = syscall.Errno(WINHTTP_ERROR_BASE + 11)
|
||||
ERROR_WINHTTP_SHUTDOWN = syscall.Errno(WINHTTP_ERROR_BASE + 12)
|
||||
ERROR_WINHTTP_LOGIN_FAILURE = syscall.Errno(WINHTTP_ERROR_BASE + 15)
|
||||
ERROR_WINHTTP_OPERATION_CANCELLED = syscall.Errno(WINHTTP_ERROR_BASE + 17)
|
||||
ERROR_WINHTTP_INCORRECT_HANDLE_TYPE = syscall.Errno(WINHTTP_ERROR_BASE + 18)
|
||||
ERROR_WINHTTP_INCORRECT_HANDLE_STATE = syscall.Errno(WINHTTP_ERROR_BASE + 19)
|
||||
ERROR_WINHTTP_CANNOT_CONNECT = syscall.Errno(WINHTTP_ERROR_BASE + 29)
|
||||
ERROR_WINHTTP_CONNECTION_ERROR = syscall.Errno(WINHTTP_ERROR_BASE + 30)
|
||||
ERROR_WINHTTP_RESEND_REQUEST = syscall.Errno(WINHTTP_ERROR_BASE + 32)
|
||||
ERROR_WINHTTP_SECURE_CERT_DATE_INVALID = syscall.Errno(WINHTTP_ERROR_BASE + 37)
|
||||
ERROR_WINHTTP_SECURE_CERT_CN_INVALID = syscall.Errno(WINHTTP_ERROR_BASE + 38)
|
||||
ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED = syscall.Errno(WINHTTP_ERROR_BASE + 44)
|
||||
ERROR_WINHTTP_SECURE_INVALID_CA = syscall.Errno(WINHTTP_ERROR_BASE + 45)
|
||||
ERROR_WINHTTP_SECURE_CERT_REV_FAILED = syscall.Errno(WINHTTP_ERROR_BASE + 57)
|
||||
ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN = syscall.Errno(WINHTTP_ERROR_BASE + 100)
|
||||
ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND = syscall.Errno(WINHTTP_ERROR_BASE + 101)
|
||||
ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND = syscall.Errno(WINHTTP_ERROR_BASE + 102)
|
||||
ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN = syscall.Errno(WINHTTP_ERROR_BASE + 103)
|
||||
ERROR_WINHTTP_HEADER_NOT_FOUND = syscall.Errno(WINHTTP_ERROR_BASE + 150)
|
||||
ERROR_WINHTTP_INVALID_SERVER_RESPONSE = syscall.Errno(WINHTTP_ERROR_BASE + 152)
|
||||
ERROR_WINHTTP_INVALID_HEADER = syscall.Errno(WINHTTP_ERROR_BASE + 153)
|
||||
ERROR_WINHTTP_INVALID_QUERY_REQUEST = syscall.Errno(WINHTTP_ERROR_BASE + 154)
|
||||
ERROR_WINHTTP_HEADER_ALREADY_EXISTS = syscall.Errno(WINHTTP_ERROR_BASE + 155)
|
||||
ERROR_WINHTTP_REDIRECT_FAILED = syscall.Errno(WINHTTP_ERROR_BASE + 156)
|
||||
ERROR_WINHTTP_SECURE_CHANNEL_ERROR = syscall.Errno(WINHTTP_ERROR_BASE + 157)
|
||||
ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT = syscall.Errno(WINHTTP_ERROR_BASE + 166)
|
||||
ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT = syscall.Errno(WINHTTP_ERROR_BASE + 167)
|
||||
ERROR_WINHTTP_SECURE_INVALID_CERT = syscall.Errno(WINHTTP_ERROR_BASE + 169)
|
||||
ERROR_WINHTTP_SECURE_CERT_REVOKED = syscall.Errno(WINHTTP_ERROR_BASE + 170)
|
||||
ERROR_WINHTTP_NOT_INITIALIZED = syscall.Errno(WINHTTP_ERROR_BASE + 172)
|
||||
ERROR_WINHTTP_SECURE_FAILURE = syscall.Errno(WINHTTP_ERROR_BASE + 175)
|
||||
ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR = syscall.Errno(WINHTTP_ERROR_BASE + 178)
|
||||
ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE = syscall.Errno(WINHTTP_ERROR_BASE + 179)
|
||||
ERROR_WINHTTP_AUTODETECTION_FAILED = syscall.Errno(WINHTTP_ERROR_BASE + 180)
|
||||
ERROR_WINHTTP_HEADER_COUNT_EXCEEDED = syscall.Errno(WINHTTP_ERROR_BASE + 181)
|
||||
ERROR_WINHTTP_HEADER_SIZE_OVERFLOW = syscall.Errno(WINHTTP_ERROR_BASE + 182)
|
||||
ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW = syscall.Errno(WINHTTP_ERROR_BASE + 183)
|
||||
ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW = syscall.Errno(WINHTTP_ERROR_BASE + 184)
|
||||
ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY = syscall.Errno(WINHTTP_ERROR_BASE + 185)
|
||||
ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY = syscall.Errno(WINHTTP_ERROR_BASE + 186)
|
||||
WINHTTP_ERROR_LAST = (WINHTTP_ERROR_BASE + 186)
|
||||
)
|
||||
|
||||
const (
|
||||
SECURITY_FLAG_IGNORE_UNKNOWN_CA = 0x00000100
|
||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID = 0x00002000
|
||||
SECURITY_FLAG_IGNORE_CERT_CN_INVALID = 0x00001000
|
||||
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE = 0x00000200
|
||||
SECURITY_FLAG_SECURE = 0x00000001
|
||||
SECURITY_FLAG_STRENGTH_WEAK = 0x10000000
|
||||
SECURITY_FLAG_STRENGTH_MEDIUM = 0x40000000
|
||||
SECURITY_FLAG_STRENGTH_STRONG = 0x20000000
|
||||
)
|
||||
|
||||
const (
|
||||
INTERNET_DEFAULT_PORT = 0
|
||||
INTERNET_DEFAULT_HTTP_PORT = 80
|
||||
INTERNET_DEFAULT_HTTPS_PORT = 443
|
||||
|
||||
WINHTTP_FLAG_ASYNC = 0x10000000
|
||||
|
||||
WINHTTP_FLAG_SECURE = 0x00800000
|
||||
WINHTTP_FLAG_ESCAPE_PERCENT = 0x00000004
|
||||
WINHTTP_FLAG_NULL_CODEPAGE = 0x00000008
|
||||
WINHTTP_FLAG_BYPASS_PROXY_CACHE = 0x00000100
|
||||
WINHTTP_FLAG_REFRESH = 0x00000100
|
||||
WINHTTP_FLAG_ESCAPE_DISABLE = 0x00000040
|
||||
WINHTTP_FLAG_ESCAPE_DISABLE_QUERY = 0x00000080
|
||||
|
||||
WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001
|
||||
WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002
|
||||
WINHTTP_AUTOPROXY_HOST_KEEPCASE = 0x00000004
|
||||
WINHTTP_AUTOPROXY_HOST_LOWERCASE = 0x00000008
|
||||
WINHTTP_AUTOPROXY_RUN_INPROCESS = 0x00010000
|
||||
WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = 0x00020000
|
||||
WINHTTP_AUTOPROXY_NO_DIRECTACCESS = 0x00040000
|
||||
WINHTTP_AUTOPROXY_NO_CACHE_CLIENT = 0x00080000
|
||||
WINHTTP_AUTOPROXY_NO_CACHE_SVC = 0x00100000
|
||||
|
||||
WINHTTP_AUTOPROXY_SORT_RESULTS = 0x00400000
|
||||
|
||||
WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001
|
||||
WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002
|
||||
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0
|
||||
WINHTTP_ACCESS_TYPE_NO_PROXY = 1
|
||||
WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3
|
||||
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4
|
||||
|
||||
WINHTTP_NO_PROXY_NAME = 0
|
||||
WINHTTP_NO_PROXY_BYPASS = 0
|
||||
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS = 0
|
||||
WINHTTP_NO_REQUEST_DATA = 0
|
||||
|
||||
WINHTTP_QUERY_MIME_VERSION = 0
|
||||
WINHTTP_QUERY_CONTENT_TYPE = 1
|
||||
WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING = 2
|
||||
WINHTTP_QUERY_CONTENT_ID = 3
|
||||
WINHTTP_QUERY_CONTENT_DESCRIPTION = 4
|
||||
WINHTTP_QUERY_CONTENT_LENGTH = 5
|
||||
WINHTTP_QUERY_CONTENT_LANGUAGE = 6
|
||||
WINHTTP_QUERY_ALLOW = 7
|
||||
WINHTTP_QUERY_PUBLIC = 8
|
||||
WINHTTP_QUERY_DATE = 9
|
||||
WINHTTP_QUERY_EXPIRES = 10
|
||||
WINHTTP_QUERY_LAST_MODIFIED = 11
|
||||
WINHTTP_QUERY_MESSAGE_ID = 12
|
||||
WINHTTP_QUERY_URI = 13
|
||||
WINHTTP_QUERY_DERIVED_FROM = 14
|
||||
WINHTTP_QUERY_COST = 15
|
||||
WINHTTP_QUERY_LINK = 16
|
||||
WINHTTP_QUERY_PRAGMA = 17
|
||||
WINHTTP_QUERY_VERSION = 18 // special: part of status line
|
||||
WINHTTP_QUERY_STATUS_CODE = 19 // special: part of status line
|
||||
WINHTTP_QUERY_STATUS_TEXT = 20 // special: part of status line
|
||||
WINHTTP_QUERY_RAW_HEADERS = 21 // special: all headers as ASCIIZ
|
||||
WINHTTP_QUERY_RAW_HEADERS_CRLF = 22 // special: all headers
|
||||
WINHTTP_QUERY_CONNECTION = 23
|
||||
WINHTTP_QUERY_ACCEPT = 24
|
||||
WINHTTP_QUERY_ACCEPT_CHARSET = 25
|
||||
WINHTTP_QUERY_ACCEPT_ENCODING = 26
|
||||
WINHTTP_QUERY_ACCEPT_LANGUAGE = 27
|
||||
WINHTTP_QUERY_AUTHORIZATION = 28
|
||||
WINHTTP_QUERY_CONTENT_ENCODING = 29
|
||||
WINHTTP_QUERY_FORWARDED = 30
|
||||
WINHTTP_QUERY_FROM = 31
|
||||
WINHTTP_QUERY_IF_MODIFIED_SINCE = 32
|
||||
WINHTTP_QUERY_LOCATION = 33
|
||||
WINHTTP_QUERY_ORIG_URI = 34
|
||||
WINHTTP_QUERY_REFERER = 35
|
||||
WINHTTP_QUERY_RETRY_AFTER = 36
|
||||
WINHTTP_QUERY_SERVER = 37
|
||||
WINHTTP_QUERY_TITLE = 38
|
||||
WINHTTP_QUERY_USER_AGENT = 39
|
||||
WINHTTP_QUERY_WWW_AUTHENTICATE = 40
|
||||
WINHTTP_QUERY_PROXY_AUTHENTICATE = 41
|
||||
WINHTTP_QUERY_ACCEPT_RANGES = 42
|
||||
WINHTTP_QUERY_SET_COOKIE = 43
|
||||
WINHTTP_QUERY_COOKIE = 44
|
||||
WINHTTP_QUERY_REQUEST_METHOD = 45 // special: GET/POST etc.
|
||||
WINHTTP_QUERY_REFRESH = 46
|
||||
WINHTTP_QUERY_CONTENT_DISPOSITION = 47
|
||||
|
||||
//
|
||||
// HTTP 1.1 defined headers
|
||||
//
|
||||
|
||||
WINHTTP_QUERY_AGE = 48
|
||||
WINHTTP_QUERY_CACHE_CONTROL = 49
|
||||
WINHTTP_QUERY_CONTENT_BASE = 50
|
||||
WINHTTP_QUERY_CONTENT_LOCATION = 51
|
||||
WINHTTP_QUERY_CONTENT_MD5 = 52
|
||||
WINHTTP_QUERY_CONTENT_RANGE = 53
|
||||
WINHTTP_QUERY_ETAG = 54
|
||||
WINHTTP_QUERY_HOST = 55
|
||||
WINHTTP_QUERY_IF_MATCH = 56
|
||||
WINHTTP_QUERY_IF_NONE_MATCH = 57
|
||||
WINHTTP_QUERY_IF_RANGE = 58
|
||||
WINHTTP_QUERY_IF_UNMODIFIED_SINCE = 59
|
||||
WINHTTP_QUERY_MAX_FORWARDS = 60
|
||||
WINHTTP_QUERY_PROXY_AUTHORIZATION = 61
|
||||
WINHTTP_QUERY_RANGE = 62
|
||||
WINHTTP_QUERY_TRANSFER_ENCODING = 63
|
||||
WINHTTP_QUERY_UPGRADE = 64
|
||||
WINHTTP_QUERY_VARY = 65
|
||||
WINHTTP_QUERY_VIA = 66
|
||||
WINHTTP_QUERY_WARNING = 67
|
||||
WINHTTP_QUERY_EXPECT = 68
|
||||
WINHTTP_QUERY_PROXY_CONNECTION = 69
|
||||
WINHTTP_QUERY_UNLESS_MODIFIED_SINCE = 70
|
||||
|
||||
WINHTTP_HEADER_NAME_BY_INDEX = ""
|
||||
WINHTTP_NO_OUTPUT_BUFFER = 0
|
||||
WINHTTP_NO_HEADER_INDEX = 0
|
||||
|
||||
WINHTTP_OPTION_CALLBACK = 1
|
||||
WINHTTP_OPTION_RESOLVE_TIMEOUT = 2
|
||||
WINHTTP_OPTION_CONNECT_TIMEOUT = 3
|
||||
WINHTTP_OPTION_CONNECT_RETRIES = 4
|
||||
WINHTTP_OPTION_SEND_TIMEOUT = 5
|
||||
WINHTTP_OPTION_RECEIVE_TIMEOUT = 6
|
||||
WINHTTP_OPTION_RECEIVE_RESPONSE_TIMEOUT = 7
|
||||
WINHTTP_OPTION_HANDLE_TYPE = 9
|
||||
WINHTTP_OPTION_READ_BUFFER_SIZE = 12
|
||||
WINHTTP_OPTION_WRITE_BUFFER_SIZE = 13
|
||||
WINHTTP_OPTION_PARENT_HANDLE = 21
|
||||
WINHTTP_OPTION_EXTENDED_ERROR = 24
|
||||
WINHTTP_OPTION_SECURITY_FLAGS = 31
|
||||
WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT = 32
|
||||
WINHTTP_OPTION_URL = 34
|
||||
WINHTTP_OPTION_SECURITY_KEY_BITNESS = 36
|
||||
WINHTTP_OPTION_PROXY = 38
|
||||
WINHTTP_OPTION_USER_AGENT = 41
|
||||
WINHTTP_OPTION_CONTEXT_VALUE = 45
|
||||
WINHTTP_OPTION_CLIENT_CERT_CONTEXT = 47
|
||||
WINHTTP_OPTION_REQUEST_PRIORITY = 58
|
||||
WINHTTP_OPTION_HTTP_VERSION = 59
|
||||
WINHTTP_OPTION_DISABLE_FEATURE = 63
|
||||
WINHTTP_OPTION_CODEPAGE = 68
|
||||
WINHTTP_OPTION_MAX_CONNS_PER_SERVER = 73
|
||||
WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER = 74
|
||||
WINHTTP_OPTION_AUTOLOGON_POLICY = 77
|
||||
WINHTTP_OPTION_SERVER_CERT_CONTEXT = 78
|
||||
WINHTTP_OPTION_ENABLE_FEATURE = 79
|
||||
WINHTTP_OPTION_WORKER_THREAD_COUNT = 80
|
||||
WINHTTP_OPTION_PASSPORT_COBRANDING_TEXT = 81
|
||||
WINHTTP_OPTION_PASSPORT_COBRANDING_URL = 82
|
||||
WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH = 83
|
||||
WINHTTP_OPTION_SECURE_PROTOCOLS = 84
|
||||
WINHTTP_OPTION_ENABLETRACING = 85
|
||||
WINHTTP_OPTION_PASSPORT_SIGN_OUT = 86
|
||||
WINHTTP_OPTION_PASSPORT_RETURN_URL = 87
|
||||
WINHTTP_OPTION_REDIRECT_POLICY = 88
|
||||
WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS = 89
|
||||
WINHTTP_OPTION_MAX_HTTP_STATUS_CONTINUE = 90
|
||||
WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE = 91
|
||||
WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE = 92
|
||||
WINHTTP_OPTION_CONNECTION_INFO = 93
|
||||
WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST = 94
|
||||
WINHTTP_OPTION_SPN = 96
|
||||
WINHTTP_OPTION_GLOBAL_PROXY_CREDS = 97
|
||||
WINHTTP_OPTION_GLOBAL_SERVER_CREDS = 98
|
||||
WINHTTP_OPTION_UNLOAD_NOTIFY_EVENT = 99
|
||||
WINHTTP_OPTION_REJECT_USERPWD_IN_URL = 100
|
||||
WINHTTP_OPTION_USE_GLOBAL_SERVER_CREDENTIALS = 101
|
||||
WINHTTP_LAST_OPTION = WINHTTP_OPTION_USE_GLOBAL_SERVER_CREDENTIALS
|
||||
WINHTTP_OPTION_USERNAME = 0x1000
|
||||
WINHTTP_OPTION_PASSWORD = 0x1001
|
||||
WINHTTP_OPTION_PROXY_USERNAME = 0x1002
|
||||
WINHTTP_OPTION_PROXY_PASSWORD = 0x1003
|
||||
WINHTTP_ADDREQ_INDEX_MASK = 0x0000FFFF
|
||||
WINHTTP_ADDREQ_FLAGS_MASK = 0xFFFF0000
|
||||
WINHTTP_ADDREQ_FLAG_ADD_IF_NEW = 0x10000000
|
||||
WINHTTP_ADDREQ_FLAG_ADD = 0x20000000
|
||||
WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA = 0x40000000
|
||||
WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON = 0x01000000
|
||||
WINHTTP_ADDREQ_FLAG_COALESCE = WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA
|
||||
WINHTTP_ADDREQ_FLAG_REPLACE = 0x80000000
|
||||
|
||||
WINHTTP_CALLBACK_STATUS_RESOLVING_NAME = 0x00000001
|
||||
WINHTTP_CALLBACK_STATUS_NAME_RESOLVED = 0x00000002
|
||||
WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER = 0x00000004
|
||||
WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER = 0x00000008
|
||||
WINHTTP_CALLBACK_STATUS_SENDING_REQUEST = 0x00000010
|
||||
WINHTTP_CALLBACK_STATUS_REQUEST_SENT = 0x00000020
|
||||
WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE = 0x00000040
|
||||
WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED = 0x00000080
|
||||
WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION = 0x00000100
|
||||
WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED = 0x00000200
|
||||
WINHTTP_CALLBACK_STATUS_HANDLE_CREATED = 0x00000400
|
||||
WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING = 0x00000800
|
||||
WINHTTP_CALLBACK_STATUS_DETECTING_PROXY = 0x00001000
|
||||
WINHTTP_CALLBACK_STATUS_REDIRECT = 0x00004000
|
||||
WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE = 0x00008000
|
||||
WINHTTP_CALLBACK_STATUS_SECURE_FAILURE = 0x00010000
|
||||
WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE = 0x00020000
|
||||
WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = 0x00040000
|
||||
WINHTTP_CALLBACK_STATUS_READ_COMPLETE = 0x00080000
|
||||
WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE = 0x00100000
|
||||
WINHTTP_CALLBACK_STATUS_REQUEST_ERROR = 0x00200000
|
||||
WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE = 0x00400000
|
||||
WINHTTP_CALLBACK_FLAG_RESOLVE_NAME = WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | WINHTTP_CALLBACK_STATUS_NAME_RESOLVED
|
||||
WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER = WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER
|
||||
)
|
||||
|
||||
const (
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED = 0x00000001
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT = 0x00000002
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED = 0x00000004
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA = 0x00000008
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID = 0x00000010
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID = 0x00000020
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE = 0x00000040
|
||||
WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR = 0x80000000
|
||||
)
|
||||
|
||||
// Constants we're missing from the Windows syscall package.
|
||||
const (
|
||||
ERROR_INVALID_PARAMETER syscall.Errno = 0x00000057
|
||||
)
|
||||
@@ -1,486 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package winhttp
|
||||
|
||||
// Package winhttp provides a Golang-ish interface to the WinHTTP
|
||||
// API. Because WinHTTP has a somewhat consistent pattern of use that
|
||||
// contrasts with what ideomatic Golang expects, there will still be
|
||||
// some rough edges (see GetOption and SetOption for examples), but
|
||||
// this package should enable someone with basic understanding of
|
||||
// WinHTTP and knowledge of Go to be able to use it to produce
|
||||
// software making use of winhttp.dll.
|
||||
//
|
||||
// Some notes:
|
||||
// - Error handling may look a little weird. As per
|
||||
// https://godoc.org/golang.org/x/sys/windows#LazyProc.Call , we
|
||||
// *must* check the primary return value to see if the error is
|
||||
// valid. To be more Golang-y, we do that here and return nil for
|
||||
// errors when we don't have an error condition.
|
||||
// - We currently do not support WinHTTP in async operation.
|
||||
// - To avoid constantly looking up procs, we cache them here in
|
||||
// private globals.
|
||||
// - To help mesh one's understanding of what's going on here against
|
||||
// MSDN documentation, we're using field and argument names from MSDN.
|
||||
// - Arguments and struct fields marked as reserved in MSDN are either
|
||||
// not present in function arguments here or not exported in struct
|
||||
// types. We have to include them in structs to preserve memory
|
||||
// layouts that Windows will expect.
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// HInternet is an internet handle given to us by WinHttp.
|
||||
type HInternet uintptr
|
||||
|
||||
var wh = windows.NewLazySystemDLL("winhttp.dll")
|
||||
var k32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
|
||||
var gF = k32.NewProc("GlobalFree")
|
||||
|
||||
func globalFree(ptr *uint16) {
|
||||
if ptr != nil {
|
||||
gF.Call(uintptr(unsafe.Pointer(ptr)))
|
||||
}
|
||||
}
|
||||
|
||||
type WinHttpCurrentUserIeProxyConfig struct {
|
||||
FAutoDetect bool
|
||||
LpszAutoConfigUrl *uint16
|
||||
LpszProxy *uint16
|
||||
LpszProxyBypass *uint16
|
||||
}
|
||||
|
||||
func FreeWinHttpCurrentUserIeProxyConfig(cfg *WinHttpCurrentUserIeProxyConfig) {
|
||||
globalFree(cfg.LpszAutoConfigUrl)
|
||||
globalFree(cfg.LpszProxy)
|
||||
globalFree(cfg.LpszProxyBypass)
|
||||
}
|
||||
|
||||
type WinHttpAutoproxyOptions struct {
|
||||
DwFlags uint32
|
||||
DwAutoDetectFlags uint32
|
||||
LpszAutoConfigUrl *uint16
|
||||
lpvReserved uintptr
|
||||
dwReserved uint32
|
||||
FAutoLogonIfChallenged bool
|
||||
}
|
||||
|
||||
type WinHttpProxyInfo struct {
|
||||
DwAccessType uint32
|
||||
LpszProxy *uint16
|
||||
LpszProxyBypass *uint16
|
||||
}
|
||||
|
||||
func FreeWinHttpProxyInfo(cfg *WinHttpProxyInfo) {
|
||||
globalFree(cfg.LpszProxy)
|
||||
globalFree(cfg.LpszProxyBypass)
|
||||
}
|
||||
|
||||
var closeHandle = wh.NewProc("WinHttpCloseHandle")
|
||||
|
||||
// CloseHandle will close an hInternet handle. It is OK if the handle
|
||||
// is already NULL.
|
||||
func CloseHandle(hInternet HInternet) {
|
||||
if hInternet != 0 {
|
||||
closeHandle.Call(uintptr(hInternet))
|
||||
}
|
||||
}
|
||||
|
||||
var winHttpOpen = wh.NewProc("WinHttpOpen")
|
||||
|
||||
// Open creates a new WinHTTP Session for us.
|
||||
func Open(userAgent string, accessType uint32, proxyName string, proxyBypass string, flags uint32) (HInternet, error) {
|
||||
var pname *uint16
|
||||
var pbypass *uint16
|
||||
if proxyName != "" {
|
||||
pname = windows.StringToUTF16Ptr(proxyName)
|
||||
}
|
||||
if proxyBypass != "" {
|
||||
pbypass = windows.StringToUTF16Ptr(proxyBypass)
|
||||
}
|
||||
h, _, err := winHttpOpen.Call(
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(userAgent))),
|
||||
uintptr(accessType),
|
||||
uintptr(unsafe.Pointer(pname)),
|
||||
uintptr(unsafe.Pointer(pbypass)),
|
||||
uintptr(flags),
|
||||
)
|
||||
|
||||
if h != 0 {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return HInternet(h), err
|
||||
}
|
||||
|
||||
var queryOption = wh.NewProc("WinHttpQueryOption")
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// QueryOption queries an option on the specified handle.
|
||||
//
|
||||
// Note that because we don't know what type of data we'll be getting
|
||||
// via buffer, this function is tagged with a pragma to behave
|
||||
// properly when using the form uintptr(unsafe.Pointer(&obj)) in its
|
||||
// function call, similar to the wired-in compiler tricks for
|
||||
// syscall.Syscall(). See the documentation to unsafe.Pointer
|
||||
// (https://golang.org/pkg/unsafe/#Pointer) for more information.
|
||||
func QueryOption(hInternet HInternet, option uint32, buffer uintptr, bufferLength *uint32) error {
|
||||
r, _, err := queryOption.Call(
|
||||
uintptr(hInternet),
|
||||
uintptr(option),
|
||||
buffer,
|
||||
uintptr(unsafe.Pointer(bufferLength)),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var setOption = wh.NewProc("WinHttpSetOption")
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// SetOption sets an option on the specified handle.
|
||||
//
|
||||
// Note that because we don't know what type of data we'll be getting
|
||||
// via buffer, this function is tagged with a pragma to behave
|
||||
// properly when using the form uintptr(unsafe.Pointer(&obj)) in its
|
||||
// function call, similar to the wired-in compiler tricks for
|
||||
// syscall.Syscall(). See the documentation to unsafe.Pointer
|
||||
// (https://golang.org/pkg/unsafe/#Pointer) for more information.
|
||||
func SetOption(
|
||||
hInternet HInternet,
|
||||
option uint32,
|
||||
buffer uintptr,
|
||||
bufferLength uintptr,
|
||||
) error {
|
||||
r, _, err := setOption.Call(
|
||||
uintptr(hInternet),
|
||||
uintptr(option),
|
||||
buffer,
|
||||
bufferLength,
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var setStatusCallback = wh.NewProc("WinHttpSetStatusCallback")
|
||||
|
||||
// SetStatusCallback sets a callback for status events from WinHTTP.
|
||||
//
|
||||
// See documentation on the callback function signature at
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa383917(v=vs.85).aspx
|
||||
func SetStatusCallback(
|
||||
hInternet HInternet,
|
||||
internetCallback interface{},
|
||||
notificationFlags uint32,
|
||||
) error {
|
||||
r, _, err := setStatusCallback.Call(
|
||||
uintptr(hInternet),
|
||||
syscall.NewCallback(internetCallback),
|
||||
uintptr(notificationFlags),
|
||||
0,
|
||||
)
|
||||
|
||||
// WinHttpSetStatusCallback returns a uintptr representing a
|
||||
// pointer to the previous callback or a value of -1 in the
|
||||
// case of error. For those playing along at home, we can't do
|
||||
// much with the uintptr to a function, nor can we represent
|
||||
// -1 as valid uintptr. So, we do the below to return true or
|
||||
// false for success instead.
|
||||
if r == ^uintptr(0) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var sendRequest = wh.NewProc("WinHttpSendRequest")
|
||||
|
||||
// SendRequest sends the given hRequest across the network, with
|
||||
// optionally setting additional headers or body data.
|
||||
func SendRequest(
|
||||
hRequest HInternet,
|
||||
headers string,
|
||||
optional *byte,
|
||||
optionalLength int,
|
||||
totalLength int,
|
||||
context *uint32,
|
||||
) error {
|
||||
var r uintptr
|
||||
var err error
|
||||
if headers != "" {
|
||||
r, _, err = sendRequest.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(headers))),
|
||||
^uintptr(0),
|
||||
uintptr(unsafe.Pointer(optional)),
|
||||
uintptr(optionalLength),
|
||||
uintptr(totalLength),
|
||||
uintptr(unsafe.Pointer(context)),
|
||||
)
|
||||
} else {
|
||||
r, _, err = sendRequest.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(WINHTTP_NO_ADDITIONAL_HEADERS),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(optional)),
|
||||
uintptr(optionalLength),
|
||||
uintptr(totalLength),
|
||||
uintptr(unsafe.Pointer(context)),
|
||||
)
|
||||
}
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var receiveResponse = wh.NewProc("WinHttpReceiveResponse")
|
||||
|
||||
// ReceiveResponse waits to receive the response to an HTTP request
|
||||
// sent by SendRequest.
|
||||
func ReceiveResponse(hRequest HInternet) error {
|
||||
if r, _, err := receiveResponse.Call(uintptr(hRequest), 0); r != 1 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var connect = wh.NewProc("WinHttpConnect")
|
||||
|
||||
// Connect creates an hConnect handle pointing to a specified server.
|
||||
func Connect(
|
||||
hSession HInternet,
|
||||
serverName string,
|
||||
port int,
|
||||
) (HInternet, error) {
|
||||
r, _, err := connect.Call(
|
||||
uintptr(hSession),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(serverName))),
|
||||
uintptr(port),
|
||||
0,
|
||||
)
|
||||
|
||||
if r != 0 {
|
||||
err = nil
|
||||
}
|
||||
return HInternet(r), err
|
||||
}
|
||||
|
||||
var openRequest = wh.NewProc("WinHttpOpenRequest")
|
||||
|
||||
// OpenRequest creates a new request for a given hConnect.
|
||||
func OpenRequest(
|
||||
hConnect HInternet,
|
||||
verb string,
|
||||
objectName string,
|
||||
version string,
|
||||
referrer string,
|
||||
acceptTypes []string,
|
||||
flags uint32,
|
||||
) (HInternet, error) {
|
||||
var verbptr *uint16
|
||||
if verb != "" {
|
||||
verbptr = windows.StringToUTF16Ptr(verb)
|
||||
}
|
||||
|
||||
var objptr *uint16
|
||||
if objectName != "" {
|
||||
objptr = windows.StringToUTF16Ptr(objectName)
|
||||
}
|
||||
|
||||
var verptr *uint16
|
||||
if version != "" {
|
||||
verptr = windows.StringToUTF16Ptr(version)
|
||||
}
|
||||
|
||||
var refptr *uint16
|
||||
if referrer != "" {
|
||||
refptr = windows.StringToUTF16Ptr(referrer)
|
||||
}
|
||||
|
||||
r, _, err := openRequest.Call(
|
||||
uintptr(hConnect),
|
||||
uintptr(unsafe.Pointer(verbptr)),
|
||||
uintptr(unsafe.Pointer(objptr)),
|
||||
uintptr(unsafe.Pointer(verptr)),
|
||||
uintptr(unsafe.Pointer(refptr)),
|
||||
0, // Not implementing ppwszAcceptTypes at this time.
|
||||
uintptr(flags),
|
||||
)
|
||||
|
||||
if r != 0 {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return HInternet(r), err
|
||||
}
|
||||
|
||||
var addRequestHeaders = wh.NewProc("WinHttpAddRequestHeaders")
|
||||
|
||||
// AddRequestHeaders adds HTTP headers to the specified Request.
|
||||
func AddRequestHeaders(
|
||||
hRequest HInternet,
|
||||
header string,
|
||||
modifiers uint32,
|
||||
) error {
|
||||
r, _, err := addRequestHeaders.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(header))),
|
||||
^uintptr(0),
|
||||
uintptr(modifiers),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var queryHeaders = wh.NewProc("WinHttpQueryHeaders")
|
||||
|
||||
// QueryHeaders retreives information from the headers of the
|
||||
// specified Request.
|
||||
func QueryHeaders(
|
||||
hRequest HInternet,
|
||||
infoLevel uint32,
|
||||
name string,
|
||||
buffer *uint16,
|
||||
bufferLength *uint32,
|
||||
index *uint32,
|
||||
) error {
|
||||
var nameptr *uint16
|
||||
if name != "" {
|
||||
nameptr = windows.StringToUTF16Ptr(name)
|
||||
}
|
||||
r, _, err := queryHeaders.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(infoLevel),
|
||||
uintptr(unsafe.Pointer(nameptr)),
|
||||
uintptr(unsafe.Pointer(buffer)),
|
||||
uintptr(unsafe.Pointer(bufferLength)),
|
||||
uintptr(unsafe.Pointer(index)),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var queryDataAvailable = wh.NewProc("WinHttpQueryDataAvailable")
|
||||
|
||||
// QueryDataAvailable returns if we have data available to read.
|
||||
func QueryDataAvailable(
|
||||
hRequest HInternet,
|
||||
) (uint, error) {
|
||||
var d uint32
|
||||
r, _, err := queryDataAvailable.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(unsafe.Pointer(&d)),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint(d), nil
|
||||
}
|
||||
|
||||
var readData = wh.NewProc("WinHttpReadData")
|
||||
|
||||
// ReadData reads data from the Request into buffer.
|
||||
func ReadData(
|
||||
hRequest HInternet,
|
||||
buffer []byte,
|
||||
) (int, error) {
|
||||
if len(buffer) > 0 {
|
||||
var b uint32
|
||||
r, _, err := readData.Call(
|
||||
uintptr(hRequest),
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(len(buffer)),
|
||||
uintptr(unsafe.Pointer(&b)),
|
||||
)
|
||||
if r != 1 {
|
||||
return 0, err
|
||||
}
|
||||
return int(b), nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
var getProxyForUrl = wh.NewProc("WinHttpGetProxyForUrl")
|
||||
|
||||
func GetProxyForUrl(
|
||||
hSession HInternet,
|
||||
url string,
|
||||
autoProxyOptions *WinHttpAutoproxyOptions,
|
||||
) (*WinHttpProxyInfo, error) {
|
||||
|
||||
p := new(WinHttpProxyInfo)
|
||||
|
||||
r, _, err := getProxyForUrl.Call(
|
||||
uintptr(hSession),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(url))),
|
||||
uintptr(unsafe.Pointer(autoProxyOptions)),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
var getIEProxyConfigForCurrentUser = wh.NewProc("WinHttpGetIEProxyConfigForCurrentUser")
|
||||
|
||||
func GetIEProxyConfigForCurrentUser() (*WinHttpCurrentUserIeProxyConfig, error) {
|
||||
i := new(WinHttpCurrentUserIeProxyConfig)
|
||||
r, _, err := getIEProxyConfigForCurrentUser.Call(
|
||||
uintptr(unsafe.Pointer(i)),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
var setTimeouts = wh.NewProc("WinHttpSetTimeouts")
|
||||
|
||||
func SetTimeouts(
|
||||
hInternet HInternet,
|
||||
resolveTimeout int,
|
||||
connectTimeout int,
|
||||
sendTimeout int,
|
||||
receiveTimeout int,
|
||||
) error {
|
||||
r, _, err := setTimeouts.Call(
|
||||
uintptr(hInternet),
|
||||
uintptr(resolveTimeout),
|
||||
uintptr(connectTimeout),
|
||||
uintptr(sendTimeout),
|
||||
uintptr(receiveTimeout),
|
||||
)
|
||||
|
||||
if r != 1 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Copyright (C) 2018, Rapid7 LLC, Boston, MA, USA.
|
||||
// All rights reserved. This material contains unpublished, copyrighted
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
-31
@@ -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
|
||||
}
|
||||
-302
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+1
-7
@@ -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
|
||||
|
||||
Reference in new issue
Block a user