Compare commits

..
84 Commits
Author SHA1 Message Date
moloch-- 08f3997756 Fix sliver.h -> main.h 2026-01-02 21:40:45 -06:00
moloch-- 3aebebecbb Implement RenameImport() 2026-01-02 21:22:19 -06:00
moloch-- 1d7eca8e39 Validate module path 2026-01-02 21:06:14 -06:00
moloch-- 773ee5f867 Rename sliver.go -> main.go 2026-01-02 20:55:26 -06:00
moloch-- 7608a4b0a0 Add GoPackage to ImplantConfig pb 2026-01-02 20:41:35 -06:00
moloch-- 9ecd36b7b6 Implement module rename 2026-01-02 20:33:27 -06:00
moloch-- d8fc5a6df4 Tweak project dir structure 2026-01-02 19:02:07 -06:00
moloch-- 649a6582e0 Add -mod=vendor to GoBuild 2026-01-02 18:53:31 -06:00
moloch-- 00e8a0cfca Move main implant code out of main pkg 2026-01-02 18:50:26 -06:00
moloch-- e9890bc8f9 Set limit to unauthed body req size 2026-01-02 17:32:43 -06:00
Joe 8b798f8307 Merge pull request #2083 from BishopFox/feature/mcp
Experiamental MCP Support
2026-01-02 16:25:24 -07:00
moloch-- d4728af1ea Add mcp logging 2026-01-02 16:26:01 -06:00
moloch-- d1b7d91b1a Add basic file operations to mcp 2026-01-02 12:48:35 -06:00
moloch-- cac6dced34 Tweak mcp 2026-01-02 11:14:32 -06:00
moloch-- a35bf0c1fc Implement mcp stdio option 2026-01-02 07:01:55 -06:00
moloch-- 67616e50c1 Wire rpc to mcp 2026-01-01 22:43:52 -06:00
moloch-- 54de8a2a97 Outline mcp 2026-01-01 22:32:25 -06:00
moloch-- 20916c655c Improve kick-operator command 2026-01-01 11:17:07 -06:00
Joe 6c18201ed6 Merge pull request #2081 from BishopFox/fix/rpc-errors
Improve gRPC Error Codes
2026-01-01 09:57:00 -07:00
moloch-- 29165aff0d Add /artifacts to gitignore 2026-01-01 10:04:53 -06:00
moloch-- 704285f28d Fix release workflow 2026-01-01 09:50:59 -06:00
moloch-- a007872fbc Include version history in release action 2026-01-01 09:48:56 -06:00
moloch-- c90e674f25 Merge branch 'master' into fix/rpc-errors 2026-01-01 09:40:57 -06:00
moloch-- 679b8d0c51 Add missing --permissions all flag 2026-01-01 09:40:43 -06:00
moloch-- 0a42a03052 Add missing --permissions all flag 2026-01-01 09:40:31 -06:00
moloch-- 73852e5bf2 Remove mingw, add operator --permissions flag 2026-01-01 09:37:16 -06:00
moloch-- 5bcf59e1b3 Fix installation script for amd64 minisig name 2026-01-01 09:32:02 -06:00
moloch-- 935c737c35 Implement better grpc error codes 2026-01-01 09:30:14 -06:00
moloch-- 5c04e61235 Simplify signing operation AGAIN 2025-12-31 16:25:10 -06:00
moloch-- cff7a8ce53 Simplify signing operation 2025-12-31 15:53:07 -06:00
moloch-- e6f452ea2e Simplify signing operation 2025-12-31 15:29:46 -06:00
moloch-- c5f533bc80 Simplify signing operation 2025-12-31 15:13:03 -06:00
moloch-- bc08760320 nop 2025-12-31 14:56:02 -06:00
moloch-- 9b37bee391 nop 2025-12-31 14:37:52 -06:00
moloch-- 709085238d Improve release minisign key handling 2025-12-31 14:20:12 -06:00
moloch-- 6e4240f0d9 Fix release workflow 2025-12-31 14:04:46 -06:00
moloch-- c95e5c6cd7 Implement armory forms 2025-12-31 13:48:00 -06:00
moloch-- ed3ab31741 Implement armory forms 2025-12-31 13:41:53 -06:00
moloch-- 9372b768be Update docs for minisign, remove gpg 2025-12-31 13:38:45 -06:00
moloch-- 2d7406f415 Update readme and npm 2025-12-31 13:13:44 -06:00
moloch-- 85ba0cb587 Update docs for v1.6 2025-12-31 11:33:21 -06:00
moloch-- 251871cca1 Switch to minisign for install script, add arm64 support 2025-12-31 11:15:33 -06:00
moloch-- 604318ea54 Update release workflow, add support for windows/arm64 2025-12-31 11:08:21 -06:00
moloch-- bc72b20f9b Update release workflow 2025-12-31 10:40:42 -06:00
Joe 61022e1eb1 Merge pull request #2078 from BishopFox/fix/forms
Refactor Survey to Huh
2025-12-31 09:17:11 -07:00
moloch-- a8d59daded tidy 2025-12-31 10:12:28 -06:00
moloch-- 9195376993 Refactor survey to huh 2025-12-31 10:04:27 -06:00
moloch-- a6de8e0304 Add profile forms 2025-12-31 09:13:21 -06:00
Joe d3ebd34b0d Merge pull request #2077 from BishopFox/fix/forms
Implement basic forms
2025-12-31 07:46:07 -07:00
moloch-- 310619526d Tweak settings 2025-12-31 08:40:02 -06:00
moloch-- 1990777c4b Tweak settings 2025-12-31 08:39:55 -06:00
moloch-- 9570556a87 Refactor to use debug.BuildInfo for version info 2025-12-31 08:27:09 -06:00
moloch-- b563919c19 Update x/crypto 2025-12-31 08:03:33 -06:00
moloch-- f1858ee8f3 Add 'generate beacon' form 2025-12-31 07:58:27 -06:00
moloch-- 46a8313a33 Tweak generate form 2025-12-31 07:51:57 -06:00
Joe 22454f3385 Merge pull request #2070 from xp3s/fix/dns-beacon-close-session
fix: DNS beacon Close() should call CloseSession() to prevent UDP port leak
2025-12-30 22:43:38 -07:00
moloch-- 868b2c294c Merge branch 'master' into fix/dnsc2 2025-12-30 23:04:32 -06:00
moloch-- 9827b4df4b Add forms pkg 2025-12-30 23:04:03 -06:00
moloch-- a48309a813 Add forms pkg 2025-12-30 23:03:55 -06:00
moloch-- 5df5dec5d4 Add forms pkg 2025-12-30 23:03:46 -06:00
Joe a0fb2a229f Merge branch 'master' into fix/dns-beacon-close-session 2025-12-30 21:59:35 -07:00
Joe 3ff3ef2ed4 Merge pull request #2076 from BishopFox/fix/dnsc2
Add cleanup to unit tests to avoid excessive disk usage
2025-12-30 21:59:21 -07:00
moloch-- 73cc8c38ff Add cleanup to unit tests to avoid excessive disk usage 2025-12-30 17:47:49 -06:00
Joe 896c763166 Merge branch 'master' into fix/dns-beacon-close-session 2025-12-30 16:42:38 -07:00
Joe ec7e2facbc Merge pull request #2075 from BishopFox/fix/dnsc2
Improvements to DNS C2, Port go-assest.sh to Golang
2025-12-30 15:37:37 -07:00
Joe 4969c2dc51 Merge branch 'master' into fix/dnsc2 2025-12-30 15:36:27 -07:00
moloch-- 94cb60b1f7 Updates to tests and implementation of dns c2 2025-12-30 16:35:37 -06:00
moloch-- 33fcfd54ef Update makefile to make goos/goarch override easier 2025-12-30 13:04:52 -06:00
moloch-- 10bb248e50 Code cleanup 2025-12-30 12:18:05 -06:00
moloch-- e023fd507e Port go-assets.sh to golang with go run 2025-12-30 11:55:56 -06:00
moloch-- 1a9f0f38dd Port go-assets.sh to golang with go run 2025-12-30 10:56:02 -06:00
moloch-- 02831a80bf Cleanup outputs of go-assets.sh 2025-12-30 10:26:24 -06:00
moloch-- 9ccff1798e Expand and update dns c2 unit tests 2025-12-30 09:46:49 -06:00
Joe cd5ef8b5c6 Merge pull request #2063 from BishopFox/3e11dependabot/npm_and_yarn/docs/sliver-docs/next-15.5.9
Bump next from 15.5.7 to 15.5.9 in /docs/sliver-docs
2025-12-30 08:12:38 -07:00
moloch-- dda1fdbbc2 Update dns unit tests 2025-12-30 08:52:26 -06:00
Joe 3b49df5662 Merge pull request #2067 from BishopFox/350cdependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-29 21:44:47 -07:00
Joe 32be71ff8e Merge pull request #2069 from BishopFox/0d1adependabot/github_actions/actions/download-artifact-7
Bump actions/download-artifact from 6 to 7
2025-12-29 21:44:33 -07:00
Joe 3f77da1524 Merge pull request #2068 from BishopFox/f7cedependabot/github_actions/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-29 21:44:19 -07:00
Joe bae3e9e45d Merge pull request #2074 from BishopFox/polite-zig
Update zig download to be more polite and secure issue #2065
2025-12-29 14:52:04 -07:00
xp3s 12f12218e0 fix: DNS beacon Close() should call CloseSession() to prevent UDP port leak
The DNS beacon's Close() method was empty, causing DNS client worker
goroutines and UDP connections to accumulate over time. This fix ensures
that CloseSession() is called to properly release resources when the
beacon connection is closed.
2025-12-15 17:13:19 +08:00
dependabot[bot] fae7e211a7 Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:52 +00:00
dependabot[bot] 346da14431 Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:47 +00:00
dependabot[bot] 15174f6c31 Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:44 +00:00
dependabot[bot] f29df040bb Bump next from 15.5.7 to 15.5.9 in /docs/sliver-docs
Bumps [next](https://github.com/vercel/next.js) from 15.5.7 to 15.5.9.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.5.7...v15.5.9)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.5.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-12 01:53:24 +00:00
797 changed files with 100319 additions and 25003 deletions

No files matched your search

+69 -115
View File
@@ -2,136 +2,90 @@ name: Release
on:
push:
branches: [master]
branches:
- master
permissions:
contents: write
jobs:
servers-build:
name: Release Build Server
if: startsWith( github.ref, 'refs/tags/v')
tag-check:
name: Check Release Tag
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Check Out Code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Find Semver Tag
id: tag
run: |
git fetch --prune --tags -f
tag=$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
echo "tag=$tag" >> "$GITHUB_OUTPUT"
release:
name: Release
needs: tag-check
if: needs.tag-check.outputs.tag != ''
runs-on: ubuntu-latest
environment: release
timeout-minutes: 45
steps:
- name: Check Out Code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
- name: OS Packages
- name: Install Minisign
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
- name: Build Clients
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip
make clients
mkdir -p artifacts
mv sliver-client_* artifacts/
- name: Check Out Code
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
- name: Make
run: make servers
- name: Server Artifacts
uses: actions/upload-artifact@v5
with:
name: servers
path: ./sliver-server_*
clients-build:
name: Release Build Client
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
- name: Check Out Code
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
- name: Make
run: make clients
- name: Client Artifacts
uses: actions/upload-artifact@v5
with:
name: clients
path: ./sliver-client_*
tagged-release:
needs: [servers-build, clients-build]
name: "Tagged Release"
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- run: mkdir -p ./builds
- uses: actions/download-artifact@v6
with:
path: ./builds
- name: Extract Artifacts
- name: Build Servers
run: |
mkdir -p ./artifacts
cp ./builds/clients/* ./artifacts
cp ./builds/servers/* ./artifacts
make servers
mv sliver-server_* artifacts/
- name: GPG Key(s)
- name: Sign Artifacts
env:
MINISIGN_KEY: ${{ secrets.MINISIGN_SLIVER_RELEASE_KEY }}
run: |
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG
echo "$MINISIGN_KEY" | base64 -d > minisign.key
echo "minisign.key bytes: $(wc -c < minisign.key | tr -d ' ')"
echo "minisign.key sha256: $(sha256sum minisign.key | awk '{print $1}')"
chmod 600 minisign.key
for file in artifacts/sliver-*; do
[ -f "$file" ] || continue
minisign -S -s minisign.key -m "$file" -x "$file.minisig" -W
done
rm -f minisign.key
# Linux
- name: Linux 386 (Client Only)
- name: Create Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-386.sig --detach-sig ./artifacts/sliver-client_linux-386
- name: Linux AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-amd64.sig --detach-sig ./artifacts/sliver-server_linux-amd64
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-amd64.sig --detach-sig ./artifacts/sliver-client_linux-amd64
- name: Linux ARM64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-arm64.sig --detach-sig ./artifacts/sliver-server_linux-arm64
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-arm64.sig --detach-sig ./artifacts/sliver-client_linux-arm64
tag="${{ needs.tag-check.outputs.tag }}"
if gh release view "$tag" >/dev/null 2>&1; then
echo "Release $tag already exists."
exit 0
fi
gh release create "$tag" --generate-notes
# Windows
- name: Windows 386 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-386.sig --detach-sig ./artifacts/sliver-client_windows-386.exe
- name: Windows AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows-amd64.sig --detach-sig ./artifacts/sliver-server_windows-amd64.exe
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-amd64.sig --detach-sig ./artifacts/sliver-client_windows-amd64.exe
- name: Windows ARM64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-arm64.sig --detach-sig ./artifacts/sliver-client_windows-arm64.exe
# MacOS
- name: MacOS AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-amd64.sig --detach-sig ./artifacts/sliver-server_macos-amd64
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-amd64.sig --detach-sig ./artifacts/sliver-client_macos-amd64
- 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
# FreeBSD
- name: FreeBSD AMD64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-amd64.sig --detach-sig ./artifacts/sliver-client_freebsd-amd64.exe
- name: FreeBSD ARM64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-arm64.sig --detach-sig ./artifacts/sliver-client_freebsd-arm64.exe
- name: "Publish Release"
uses: "bishopfox/action-gh-release@v1"
with:
files: |
./artifacts/*
- name: Upload Artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${{ needs.tag-check.outputs.tag }}" artifacts/* --clobber
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
uses: actions/configure-pages@v5
- name: Restore cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
.next/cache
+4
View File
@@ -2,6 +2,10 @@
/.devcontainer
/.idea
/artifacts
/artifacts/**
/Dockerfile.*
/*.sdk.tar.xz
+1 -1
View File
@@ -26,7 +26,7 @@
## Testing Guidelines
- Go tests follow standard `_test.go` naming within each package.
- Prefer `./go-tests.sh` (uses tags like `client`, `server`, `osusergo`, `netgo`, `go_sqlite`) to match CI expectations.
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
- Add or update tests when behavior changes; some server packages run longer tests.
## Commit & Pull Request Guidelines
+5 -1
View File
@@ -5,6 +5,7 @@
#
# For unit testing:
# docker build --target test .
# docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .
#
# STAGE: base
@@ -31,6 +32,9 @@ RUN cp -vv sliver-server /opt/sliver-server
## Use `--target test` in the docker build command to run this stage
FROM base AS test
ARG GO_TESTS_FLAGS=""
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
RUN apt-get update --fix-missing \
&& apt-get -y upgrade \
&& apt-get -y install \
@@ -39,7 +43,7 @@ RUN apt-get update --fix-missing \
RUN /opt/sliver-server unpack --force
### Run unit tests
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS}
# STAGE: production
## Final dockerized form of Sliver
+13 -35
View File
@@ -16,49 +16,29 @@ endif
# 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 date cut $(GO)
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
#
# Version Information
# Build 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 = 24
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
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
VERSION_PKG = github.com/bishopfox/sliver/client/version
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
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_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
# Debug builds shouldn't be stripped (-s -w flags)
LDFLAGS_DEBUG = -ldflags "-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_PUBLIC_KEY) \
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
SED_INPLACE := sed -i
@@ -83,7 +63,9 @@ endif
# If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm)
ifeq ($(MAKECMDGOALS), )
ENV += GOARCH=arm64
ifeq ($(origin GOARCH), undefined)
ENV += GOARCH=arm64
endif
endif
endif
@@ -91,12 +73,6 @@ 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_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif
@@ -105,9 +81,10 @@ endif
# Targets
#
.PHONY: default
default: clean .downloaded_assets validate-go-version
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
default: clean validate-go-version
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
@@ -154,8 +131,9 @@ clients: clean .downloaded_assets validate-go-version
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
.PHONY: servers
servers:
servers: clean .downloaded_assets validate-go-version
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
@@ -199,5 +177,5 @@ clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets:
./go-assets.sh
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
+3 -8
View File
@@ -6,12 +6,6 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
# v1.6.0 / `master`
**NOTE:** You are looking at the latest master branch of Sliver v1.6.0; new PRs should target this branch. However, this branch is NOT RECOMMENDED for production use yet. Please use release tagged versions for the best experience.
For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x/master` branch](https://github.com/BishopFox/sliver/tree/v1.5.x/master).
### Features
- Dynamic code generation
@@ -21,7 +15,7 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
- [Procedurally generated C2](https://sliver.sh/docs?name=HTTPS+C2) over HTTP(S)
- [DNS canary](https://sliver.sh/docs?name=DNS+C2) blue team detection
- [Secure C2](https://sliver.sh/docs?name=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)
- Fully scriptable using [Python](https://github.com/moloch--/sliver-py)
- Windows process migration, process injection, user token manipulation, etc.
- Let's Encrypt integration
- In-memory .NET assembly execution
@@ -39,7 +33,7 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
### Help!
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). We also tend to hang out in the #golang Slack channel on the [Bloodhound Gang](https://bloodhoundgang.herokuapp.com/) server.
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions).
### Compile From Source
@@ -52,3 +46,4 @@ Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env python3
"""
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 os
import sys
import argparse
INFO = "\033[1m\033[36m[*]\033[0m "
WARN = "\033[1m\033[31m[!]\033[0m "
BOLD = "\033[1m"
NORM = "\033[0m"
def exec_cmd(cmd, ignore_status=False):
"""Executes a command with some sugar"""
print("\n" + INFO + " %s\n" % cmd)
status = os.system(cmd)
if status != 0 and not ignore_status:
print("\n" + WARN + "Command did not exit cleanly (%s) " % status)
if input("continue? [Y/n]: ").upper() != "Y":
sys.exit(status)
return status
def docker_rm_volumes():
exec_cmd("docker volume rm $(docker volume ls -q)", ignore_status=True)
def docker_rm_containers():
exec_cmd("docker rm $(docker ps -a -q)", ignore_status=True)
def docker_rm_images():
exec_cmd("docker rmi -f $(docker images -q)", ignore_status=True)
def docker_prune():
exec_cmd("docker image prune -f", ignore_status=True)
def docker_rm_all():
docker_rm_containers()
docker_rm_images()
docker_rm_volumes()
def build():
status = exec_cmd("docker build --target test .")
if status == 0:
print("\n\n" + INFO + "Build successful")
def main(args):
"""Execute the respective task(s) based on cli args"""
if args.rm_images:
docker_rm_images()
if args.rm_containers:
docker_rm_containers()
if args.rm_volumes:
docker_rm_volumes()
if args.rm_all:
docker_rm_all()
if not args.no_prune:
docker_prune()
if not args.no_build:
build()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="automates interactions with docker")
parser.add_argument(
"--no-build",
"-b",
help="do not run build",
dest="no_build",
action="store_true",
)
parser.add_argument(
"--no-prune",
"-np",
help="do not prune images",
dest="no_prune",
action="store_true",
)
parser.add_argument(
"--rm-all",
"-rma",
help="rm all containers, images, and volumes",
dest="rm_all",
action="store_true",
)
parser.add_argument(
"--rm-containers",
"-rmc",
help="rm all containers",
dest="rm_containers",
action="store_true",
)
parser.add_argument(
"--rm-images",
"-rmi",
help="rm all images",
dest="rm_images",
action="store_true",
)
parser.add_argument(
"--rm-volumes",
"-rmv",
help="rm dangling volumes only",
dest="rm_volumes",
action="store_true",
)
try:
main(parser.parse_args())
except KeyboardInterrupt:
print("\n\n" + WARN + "User stopped process")
+12
View File
@@ -73,6 +73,18 @@ func GetConsoleLogsDir() string {
return consoleLogsDir
}
// GetMCPLogsDir - Get the Sliver client MCP logs dir ~/.sliver-client/logs/mcp/
func GetMCPLogsDir() string {
mcpLogsDir := filepath.Join(GetClientLogsDir(), "mcp")
if _, err := os.Stat(mcpLogsDir); os.IsNotExist(err) {
err = os.MkdirAll(mcpLogsDir, 0700)
if err != nil {
log.Fatal(err)
}
}
return mcpLogsDir
}
func assetVersion() string {
appDir := GetRootAppDir()
data, err := os.ReadFile(filepath.Join(appDir, versionFileName))
+3
View File
@@ -69,6 +69,9 @@ func init() {
// other command trees below, such as the implant one.
rootCmd.AddCommand(consoleCmd(con))
// MCP stdio server.
rootCmd.AddCommand(mcpCmd(con))
// Implant.
// The implant command allows users to run commands on slivers from their
// system shell. It makes use of pre-runners for connecting to the server
+10 -24
View File
@@ -23,7 +23,7 @@ import (
"sort"
"github.com/bishopfox/sliver/client/assets"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/bishopfox/sliver/client/forms"
)
func selectConfig() *assets.ClientConfig {
@@ -39,32 +39,18 @@ func selectConfig() *assets.ClientConfig {
}
}
answer := struct{ Config string }{}
qs := getPromptForConfigs(configs)
err := survey.Ask(qs, &answer)
keys := make([]string, 0, len(configs))
for key := range configs {
keys = append(keys, key)
}
sort.Strings(keys)
selection := keys[0]
err := forms.Select("Select a server:", keys, &selection)
if err != nil {
fmt.Println(err.Error())
return nil
}
return configs[answer.Config]
}
func getPromptForConfigs(configs map[string]*assets.ClientConfig) []*survey.Question {
keys := []string{}
for k := range configs {
keys = append(keys, k)
}
sort.Strings(keys)
return []*survey.Question{
{
Name: "config",
Prompt: &survey.Select{
Message: "Select a server:",
Options: keys,
Default: keys[0],
},
},
}
return configs[selection]
}
+94
View File
@@ -0,0 +1,94 @@
package cli
/*
Sliver Implant Framework
Copyright (C) 2023 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"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/bishopfox/sliver/client/transport"
"github.com/spf13/cobra"
)
func mcpCmd(con *console.SliverClient) *cobra.Command {
mcpCmd := &cobra.Command{
Use: "mcp",
Short: "Start the MCP stdio server",
RunE: func(cmd *cobra.Command, args []string) error {
return runMCP(cmd, con)
},
}
mcpCmd.Flags().String("config", "", "path to client config file")
return mcpCmd
}
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
configPath, _ := cmd.Flags().GetString("config")
config, err := loadMCPClientConfig(configPath)
if err != nil {
fmt.Printf("%s\n", err)
return nil
}
rpc, ln, err := transport.MTLSConnect(config)
if err != nil {
fmt.Printf("Connection to server failed %s\n", err)
return nil
}
defer ln.Close()
con.Rpc = rpc
go handleConnectionLost(ln)
cfg := clientmcp.DefaultConfig()
if err := clientmcp.ServeStdio(cfg, con.Rpc); err != nil {
fmt.Printf("MCP server error: %s\n", err)
return nil
}
return nil
}
func loadMCPClientConfig(path string) (*assets.ClientConfig, error) {
if path != "" {
configPath := path
if !filepath.IsAbs(configPath) {
if _, err := os.Stat(configPath); err != nil {
configPath = filepath.Join(assets.GetConfigDir(), configPath)
}
}
return assets.ReadConfig(configPath)
}
configs := assets.GetConfigs()
switch len(configs) {
case 0:
return nil, fmt.Errorf("no config files found at %s (use --config)", assets.GetConfigDir())
case 1:
for _, config := range configs {
return config, nil
}
default:
return nil, fmt.Errorf("multiple configs found; use --config to select one")
}
return nil, fmt.Errorf("no config files found")
}
+2 -1
View File
@@ -8,7 +8,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `command.go` – Provides helpers for binding Cobra commands, flag sets, and visibility filters into the client command tree.
- `server.go` – Defines the server menu composition, wiring each server-side command group onto the root console structure at runtime.
- `sliver.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
- `main.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
## Sub-packages
@@ -40,6 +40,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `licenses/` – Implements the 'licenses' command group for the Sliver client console.
- `loot/` – Implements the 'loot' command group for the Sliver client console.
- `monitor/` – Implements the 'monitor' command group for the Sliver client console.
- `mcp/` – Implements the 'mcp' command group for the Sliver client console.
- `network/` – Implements the 'network' command group for the Sliver client console.
- `operators/` – Implements the 'operators' command group for the Sliver client console.
- `pivots/` – Implements the 'pivots' command group for the Sliver client console.
+3 -5
View File
@@ -24,9 +24,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -62,8 +62,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return
}
@@ -122,8 +121,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
if promptToOverwrite {
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return nil
}
+2 -3
View File
@@ -27,11 +27,11 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"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/client/forms"
"github.com/bishopfox/sliver/client/packages"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -313,8 +313,7 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
}
con.PrintWarnf("%s", msgStr)
confirm := false
prompt := &survey.Confirm{Message: "Do you want to continue?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -24,9 +24,9 @@ import (
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -39,8 +39,7 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' alias?", name), &confirm)
if !confirm {
return
}
+1 -1
View File
@@ -37,7 +37,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
Use: consts.InstallStr,
Short: "Install an alias or extension",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
Args: cobra.ExactArgs(1),
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
ArmoryInstallCmd(cmd, con, args)
},
+188 -48
View File
@@ -26,8 +26,9 @@ import (
"os"
"path/filepath"
"slices"
"sort"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets"
@@ -35,6 +36,7 @@ import (
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/minisign"
)
@@ -51,10 +53,9 @@ const (
// ArmoryInstallCmd - The armory install command
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var promptToOverwrite bool
name := args[0]
if name == "" {
con.PrintErrorf("A package or bundle name is required")
return
names := []string{}
if len(args) > 0 {
names = []string{args[0]}
}
forceInstallation, err := cmd.Flags().GetBool("force")
if err != nil {
@@ -83,51 +84,92 @@ func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
clientConfig := parseArmoryHTTPConfig(cmd)
refresh(clientConfig)
if name == "all" {
aliases, extensions := packageManifestsInCache()
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
confirm := false
pluralAliases := "es"
if aliasCount == 1 {
pluralAliases = ""
if len(names) == 0 {
armoryFilterName := ""
if armoryPK != "" {
armoryFilterName = armoryName
}
pluralExtensions := "s"
if extCount == 1 {
pluralExtensions = ""
}
prompt := &survey.Confirm{
Message: fmt.Sprintf("Install %d alias%s and %d extension%s?",
aliasCount, pluralAliases, extCount, pluralExtensions,
),
}
survey.AskOne(prompt, &confirm)
if !confirm {
options := armoryInstallOptions(armoryPK, armoryFilterName)
if len(options) == 0 {
con.PrintInfof("No packages or bundles found\n")
return
}
promptToOverwrite = false
}
err = installPackageByName(name, armoryPK, forceInstallation, promptToOverwrite, clientConfig, con)
if err == nil {
return
}
if errors.Is(err, ErrPackageNotFound) {
bundles := bundlesInCache()
for _, bundle := range bundles {
if bundle.Name == name {
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
result, err := forms.ArmoryInstallForm(options)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Armory install form failed: %s\n", err)
return
}
// If we have made it here, then there was not a bundle or package that matched the provided name
if armoryPK == "" {
con.PrintErrorf("No package or bundle named %q was found\n", name)
if len(result.SelectedNames) == 0 {
return
}
names = result.SelectedNames
}
if len(names) == 0 {
con.PrintErrorf("A package or bundle name is required")
return
}
if slices.Contains(names, "all") {
names = []string{"all"}
}
for _, name := range names {
if name == "" {
continue
}
promptOverwrite := promptToOverwrite
if name == "all" {
aliases, extensions := packageManifestsInCache()
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
confirm := false
pluralAliases := "es"
if aliasCount == 1 {
pluralAliases = ""
}
pluralExtensions := "s"
if extCount == 1 {
pluralExtensions = ""
}
forms.Confirm(fmt.Sprintf("Install %d alias%s and %d extension%s?",
aliasCount, pluralAliases, extCount, pluralExtensions,
), &confirm)
if !confirm {
return
}
promptOverwrite = false
}
err = installPackageByName(name, armoryPK, forceInstallation, promptOverwrite, clientConfig, con)
if err == nil {
continue
}
if errors.Is(err, ErrPackageNotFound) {
bundles := bundlesInCache()
for _, bundle := range bundles {
if bundle.Name == name {
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
err = nil
break
}
}
if err == nil {
continue
}
// If we have made it here, then there was not a bundle or package that matched the provided name
if armoryPK == "" {
con.PrintErrorf("No package or bundle named %q was found\n", name)
} else {
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
}
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
} else {
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
con.PrintErrorf("Could not install package: %s\n", err)
}
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
} else {
con.PrintErrorf("Could not install package: %s\n", err)
}
}
@@ -296,11 +338,7 @@ func getPackageIDFromUser(name string, options map[string]string) string {
// Add a cancel option
optionKeys = append(optionKeys, doNotInstallOption)
options[doNotInstallOption] = doNotInstallPackageName
prompt := &survey.Select{
Message: fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name),
Options: optionKeys,
}
survey.AskOne(prompt, &selectedPackageKey)
forms.Select(fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name), optionKeys, &selectedPackageKey)
selectedPackageID := options[selectedPackageKey]
return selectedPackageID
@@ -444,12 +482,114 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
if name == "all" {
con.Printf("\n")
con.PrintInfof("Operation complete\n")
con.PrintSuccessf("Operation complete\n")
}
return nil
}
func armoryInstallOptions(armoryPK, armoryName string) []forms.ArmoryInstallOption {
options := []forms.ArmoryInstallOption{}
seen := make(map[string]bool)
packageCount := 0
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
return true
}
if cacheEntry.LastErr != nil {
return true
}
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
}
if cacheEntry.Pkg.IsAlias {
name := cacheEntry.Alias.CommandName
if name == "" {
return true
}
if seen[name] {
return true
}
label := formatArmoryInstallLabel("alias", name, cacheEntry.Alias.Help)
options = append(options, forms.ArmoryInstallOption{
Value: name,
Label: label,
})
seen[name] = true
packageCount++
return true
}
for _, command := range cacheEntry.Extension.ExtCommand {
name := command.CommandName
if name == "" {
continue
}
if seen[name] {
continue
}
label := formatArmoryInstallLabel("extension", name, command.Help)
options = append(options, forms.ArmoryInstallOption{
Value: name,
Label: label,
})
seen[name] = true
packageCount++
}
return true
})
for _, bundle := range bundlesInCache() {
if bundle.Name == "" {
continue
}
if armoryName != "" && bundle.ArmoryName != armoryName {
continue
}
if seen[bundle.Name] {
continue
}
label := fmt.Sprintf("bundle: %s (%d packages)", bundle.Name, len(bundle.Packages))
options = append(options, forms.ArmoryInstallOption{
Value: bundle.Name,
Label: label,
})
seen[bundle.Name] = true
}
if len(options) == 0 {
return options
}
sort.Slice(options, func(i, j int) bool {
return options[i].Label < options[j].Label
})
if packageCount > 0 {
options = append([]forms.ArmoryInstallOption{
{
Value: "all",
Label: "all packages (aliases + extensions)",
},
}, options...)
}
return options
}
func formatArmoryInstallLabel(kind, name, help string) string {
help = strings.TrimSpace(help)
if help == "" {
return fmt.Sprintf("%s: %s", kind, name)
}
help = strings.Join(strings.Fields(help), " ")
return fmt.Sprintf("%s: %s - %s", kind, name, help)
}
func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
if entry == nil {
return errors.New("package not found")
+2 -3
View File
@@ -8,10 +8,10 @@ import (
"strings"
"sync"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
@@ -602,8 +602,7 @@ func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
if err != nil {
con.PrintErrorf("could not modify armory: %s\n", err)
confirm := false
prompt := &survey.Confirm{Message: "Would you like to revert the properties of the armory back to their previous values?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
if !confirm {
return
} else {
+32 -71
View File
@@ -19,13 +19,11 @@ package armory
*/
import (
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -34,6 +32,7 @@ import (
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
)
@@ -87,16 +86,19 @@ func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
selectedUpdates, err = getUpdatesFromUser(con, updateKeys)
selectedUpdates, err = getUpdatesFromUser(updateKeys)
if err != nil {
con.PrintErrorf(err.Error() + "\n")
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("%s\n", err.Error())
return
}
if len(selectedUpdates) == 0 {
return
}
} else {
con.PrintSuccessf("All packages are up to date")
con.PrintSuccessf("All packages are up to date\n")
return
}
@@ -299,76 +301,35 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
con.Printf("%s\n\n", tw.Render())
}
func getUpdatesFromUser(con *console.SliverClient, updateKeys []UpdateIdentifier) (chosenUpdates []UpdateIdentifier, selectionError error) {
chosenUpdates = []UpdateIdentifier{}
func getUpdatesFromUser(updateKeys []UpdateIdentifier) ([]UpdateIdentifier, error) {
options := make([]forms.ArmoryUpdateOption, 0, len(updateKeys))
optionIndex := make(map[string]UpdateIdentifier, len(updateKeys))
var updateResponse string
for _, key := range updateKeys {
packageType := "Alias"
if key.Type == ExtensionPackage {
packageType = "Extension"
}
optionID := fmt.Sprintf("%d:%s", key.Type, key.Name)
optionLabel := fmt.Sprintf("%s (%s)", key.Name, packageType)
options = append(options, forms.ArmoryUpdateOption{
ID: optionID,
Label: optionLabel,
})
optionIndex[optionID] = key
}
con.Println("You can apply all, none, or some updates.\nTo apply some updates, specify the number of a single update, a range (1-3), or a combination of the two (1, 3-5, 7)\n")
err := survey.AskOne(&survey.Input{
Message: "Which updates would you like to apply? [A]ll, [N]one, or some:",
}, &updateResponse)
result, err := forms.ArmoryUpdateForm(options)
if err != nil {
// We do not need to set selectionError because an error here means the same thing as responding with "none"
return
return nil, err
}
updateResponse = strings.ToLower(updateResponse)
updateResponse = strings.Replace(updateResponse, " ", "", -1)
if updateResponse == "n" || updateResponse == "none" {
return
}
if updateResponse == "a" || updateResponse == "all" {
chosenUpdates = updateKeys
return
}
selections := strings.Split(updateResponse, ",")
for _, selection := range selections {
// Handle a range
if strings.Contains(selection, "-") {
rangeParts := strings.Split(selection, "-")
start, err := strconv.Atoi(rangeParts[0])
if err != nil {
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[0])
return
}
end, err := strconv.Atoi(rangeParts[1])
if err != nil {
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[1])
return
}
// Adjust for the 0 indexed slice we are working with
start -= 1
end -= 1
if start < 0 {
start = 0
}
if start > end {
selectionError = fmt.Errorf("%s is not a valid range", selection)
return
}
if end >= len(updateKeys) {
end = len(updateKeys) - 1
}
for i := start; i <= end; i++ {
chosenUpdates = append(chosenUpdates, updateKeys[i])
}
} else {
// Single entry
index, err := strconv.Atoi(selection)
if err != nil {
selectionError = fmt.Errorf("%s is not a valid range", selection)
return
}
index -= 1
if index >= 0 && index < len(updateKeys) {
chosenUpdates = append(chosenUpdates, updateKeys[index])
}
chosenUpdates := make([]UpdateIdentifier, 0, len(result.SelectedIDs))
for _, selectedID := range result.SelectedIDs {
if update, ok := optionIndex[selectedID]; ok {
chosenUpdates = append(chosenUpdates, update)
}
}
return
return chosenUpdates, nil
}
+2 -7
View File
@@ -26,9 +26,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -83,12 +82,8 @@ func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a beacon:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a beacon:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+2 -3
View File
@@ -21,10 +21,10 @@ package beacons
import (
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -71,8 +71,7 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
}
con.Println()
confirm := false
prompt := &survey.Confirm{Message: "Prune these beacons?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Prune these beacons?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -29,7 +29,6 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
@@ -38,6 +37,7 @@ import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -547,11 +547,7 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
choices = append(choices, c2profile.Name)
}
prompt := &survey.Select{
Message: "Select a c2 profile",
Options: choices,
}
survey.AskOne(prompt, &c2profile, nil)
_ = forms.Select("Select a c2 profile", choices, &c2profile)
return c2profile
}
+2 -3
View File
@@ -21,10 +21,10 @@ package clean
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -35,8 +35,7 @@ import (
func CleanCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Printf("This command will kill and remove all sessions, beacons and profiles \n")
confirm := false
prompt := &survey.Confirm{Message: "Are you sure you want to destroy everything?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -8,8 +8,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -55,11 +55,7 @@ func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.S
}
selected := ""
prompt := &survey.Select{
Message: "Select a credential:",
Options: options,
}
err = survey.AskOne(prompt, &selected)
err = forms.Select("Select a credential:", options, &selected)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -27,9 +27,9 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -123,7 +123,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -6
View File
@@ -26,9 +26,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/reeflective/readline"
"github.com/spf13/cobra"
@@ -71,12 +71,8 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a debug target:",
Options: options,
}
selected := ""
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a debug target:", options, &selected)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -23,9 +23,9 @@ import (
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
@@ -106,7 +106,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -25,9 +25,9 @@ import (
"path"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -88,7 +88,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
err = forms.Confirm("Kill and restart the process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -22,9 +22,9 @@ import (
"context"
"strconv"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
)
@@ -44,7 +44,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
core.CloseCursedProcessesByBindPort(session.ID, bindPort)
if kill {
confirm := false
err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
err := forms.Confirm("Kill the cursed process?", &confirm)
if err != nil {
con.PrintErrorf("%s", err)
return
+2 -6
View File
@@ -25,10 +25,10 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -95,12 +95,8 @@ func selectCursedProcess(con *console.SliverClient) *core.CursedProcess {
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a curse:",
Options: options,
}
selected := ""
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a curse:", options, &selected)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -3
View File
@@ -24,8 +24,8 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
@@ -82,8 +82,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
if len(assemblyArgsStr) > 256 && !inProcess {
con.PrintWarnf(" Injected .NET assembly arguments are limited to 256 characters when using the default fork/exec model.\nConsider using the --in-process flag to execute the .NET assembly in-process and work around this limitation.\n")
confirm := false
prompt := &survey.Confirm{Message: "Do you want to continue?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -23,8 +23,8 @@ import (
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
@@ -145,8 +145,7 @@ func tryCredsFromLoot(con *console.SliverClient) (string, string, []byte) {
privKey []byte
)
confirm := false
prompt := &survey.Confirm{Message: "No credentials provided, use from loot?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("No credentials provided, use from loot?", &confirm)
if confirm {
// cred, err := loot.SelectCredentials(con)
// if err != nil {
+2 -3
View File
@@ -23,9 +23,9 @@ import (
"fmt"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func ExitCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if 0 < len(sessions.Sessions) || 0 < len(beacons.Beacons) {
con.Printf("There are %d active sessions and %d active beacons.\n", len(sessions.Sessions), len(beacons.Beacons))
confirm := false
prompt := &survey.Confirm{Message: "Are you sure you want to exit?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Are you sure you want to exit?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -24,9 +24,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -92,8 +92,7 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl
if promptToOverwrite {
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -33,12 +33,12 @@ import (
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"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/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/packages"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -171,8 +171,7 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current command?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current command?", &confirm)
if !confirm {
return
}
+3 -5
View File
@@ -25,9 +25,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -40,8 +40,7 @@ func ExtensionsRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []s
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension?", name), &confirm)
if !confirm {
return
}
@@ -104,8 +103,7 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
// If path is outside extensions directory, prompt for confirmation
if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) {
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName), &confirm)
if !confirm {
// Skip the forceRemoveAll but continue with the rest
delete(loadedManifests, manifestName)
+2 -3
View File
@@ -30,12 +30,12 @@ import (
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
"gopkg.in/AlecAivazis/survey.v1"
)
func DownloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
@@ -189,8 +189,7 @@ func HandleDownloadResponse(download *sliverpb.Download, cmd *cobra.Command, arg
if _, err := os.Stat(dst); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
_ = forms.Confirm("Overwrite local file?", &overwrite)
if !overwrite {
return
}
+115
View File
@@ -1,11 +1,15 @@
package generate
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -17,6 +21,21 @@ var (
// GenerateBeaconCmd - The main command used to generate implant binaries
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateBeaconForm(cmd, con, args) {
result, err := forms.GenerateBeaconForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Generate beacon form failed: %s\n", err)
return
}
if err := applyGenerateBeaconForm(cmd, result); err != nil {
con.PrintErrorf("Failed to apply generate beacon form values: %s\n", err)
return
}
}
name, config := parseCompileFlags(cmd, con)
if config == nil {
return
@@ -67,3 +86,99 @@ func parseBeaconFlags(cmd *cobra.Command, config *clientpb.ImplantConfig) error
config.BeaconJitter = int64(time.Duration(beaconJitter) * time.Second)
return nil
}
func shouldRunGenerateBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateBeaconForm(cmd *cobra.Command, result *forms.GenerateBeaconFormResult) error {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
return err
}
}
save := strings.TrimSpace(result.Save)
if save != "" {
if err := cmd.Flags().Set("save", save); err != nil {
return err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return err
}
default:
return errors.New("unsupported C2 transport selection")
}
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
return err
}
return nil
}
func setOptionalInt64Flag(cmd *cobra.Command, name, value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return nil
}
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return err
}
if parsed < 0 {
return fmt.Errorf("%s must be 0 or greater", name)
}
return cmd.Flags().Set(name, strconv.FormatInt(parsed, 10))
}
+74 -21
View File
@@ -32,9 +32,9 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/spin"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
@@ -89,6 +89,21 @@ var (
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
result, err := forms.GenerateForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Generate form failed: %s\n", err)
return
}
if err := applyGenerateForm(cmd, result); err != nil {
con.PrintErrorf("Failed to apply generate form values: %s\n", err)
return
}
}
name, config := parseCompileFlags(cmd, con)
if config == nil {
return
@@ -117,6 +132,58 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
func shouldRunGenerateForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateForm(cmd *cobra.Command, result *forms.GenerateFormResult) error {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
return err
}
}
save := strings.TrimSpace(result.Save)
if save != "" {
if err := cmd.Flags().Set("save", save); err != nil {
return err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
return cmd.Flags().Set("mtls", c2Value)
case "wg":
return cmd.Flags().Set("wg", c2Value)
case "http":
return cmd.Flags().Set("http", c2Value)
case "dns":
return cmd.Flags().Set("dns", c2Value)
case "named-pipe":
return cmd.Flags().Set("named-pipe", c2Value)
case "tcp-pivot":
return cmd.Flags().Set("tcp-pivot", c2Value)
default:
return errors.New("unsupported C2 transport selection")
}
}
func expandPath(path string) string {
// unless path starts with ~
if len(path) == 0 || path[0] != 126 {
@@ -134,13 +201,10 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
save = expandPath(save)
fi, err := os.Stat(save)
if os.IsNotExist(err) {
con.Printf("%s does not exist\n", save)
if strings.HasSuffix(save, "/") {
con.Printf("%s is dir\n", save)
os.MkdirAll(save, 0o700)
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
} else {
con.Printf("%s is not dir\n", save)
saveDir := filepath.Dir(save)
_, err := os.Stat(saveTo)
if os.IsNotExist(err) {
@@ -152,10 +216,8 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
if fi.IsDir() {
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
} else {
con.PrintInfof("%s is not dir\n", save)
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite existing file?", &confirm)
if !confirm {
return "", errors.New("file already exists")
}
@@ -466,9 +528,8 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
console.Bold, target, console.Normal,
)
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
prompt := &survey.Confirm{Message: "Attempt to build generic implant?"}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
if !confirm {
return "", ""
}
@@ -576,7 +637,7 @@ func hasValidC2AdvancedOptions(options url.Values) (bool, error) {
if testValue != "wininet" {
return false, fmt.Errorf("C2 option \"driver\" must be empty for the default driver or \"wininet\" for the wininet driver (Windows only)")
}
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds", "force-base32":
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds":
if testValue != "true" && testValue != "false" {
return false, fmt.Errorf("C2 option \"%s\" must be a boolean value: true or false", key)
}
@@ -738,11 +799,8 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
}
if !strings.HasPrefix(uri.Path, "/pipe/") {
prompt := &survey.Confirm{
Message: fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri),
}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri), &confirm)
if !confirm {
return nil, fmt.Errorf("invalid namedpipe path: %s", uri.Path)
}
@@ -1049,8 +1107,7 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
con.PrintWarnf("For more information please read %s\n", crossCompilerInfoURL)
confirm := false
prompt := &survey.Confirm{Message: "Try to compile anyways (will likely fail)?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Try to compile anyways (will likely fail)?", &confirm)
return confirm
}
@@ -1086,11 +1143,7 @@ func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient
choices = append(choices, builder.Name)
}
choice := ""
prompt := &survey.Select{
Message: "Select an external builder:",
Options: choices,
}
survey.AskOne(prompt, &choice, nil)
_ = forms.Select("Select an external builder:", choices, &choice)
for _, builder := range builders {
if builder.Name == choice {
return builder, nil
+2 -3
View File
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -24,8 +24,7 @@ func ImplantsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' build?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' build?", name), &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -21,10 +21,10 @@ package generate
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -42,12 +42,8 @@ func ImplantsStageCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
options = append(options, name)
}
prompt := &survey.MultiSelect{
Message: "Select sessions and beacons to expose:",
Options: options,
}
selected := []string{}
survey.AskOne(prompt, &selected)
_ = forms.MultiSelect("Select sessions and beacons to expose:", options, &selected)
_, err = con.Rpc.StageImplantBuild(context.Background(), &clientpb.ImplantStageReq{Build: selected})
if err != nil {
+183 -2
View File
@@ -19,8 +19,11 @@ package generate
*/
import (
"context"
"errors"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -28,7 +31,22 @@ import (
// ProfilesNewCmd - Create a new implant profile.
func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var name string
if len(args) > 0 {
if shouldRunGenerateProfilesNewForm(cmd, con, args) {
result, err := forms.GenerateProfilesNewForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Profiles new form failed: %s\n", err)
return
}
profileName, err := applyGenerateProfilesNewForm(cmd, result)
if err != nil {
con.PrintErrorf("Failed to apply profiles new form values: %s\n", err)
return
}
name = profileName
} else if len(args) > 0 {
name = args[0]
}
// name := ctx.Args.String("name")
@@ -51,7 +69,22 @@ func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string
// ProfilesNewBeaconCmd - Create a new beacon profile.
func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var name string
if len(args) > 0 {
if shouldRunGenerateProfilesNewBeaconForm(cmd, con, args) {
result, err := forms.GenerateProfilesNewBeaconForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Profiles new beacon form failed: %s\n", err)
return
}
profileName, err := applyGenerateProfilesNewBeaconForm(cmd, result)
if err != nil {
con.PrintErrorf("Failed to apply profiles new beacon form values: %s\n", err)
return
}
name = profileName
} else if len(args) > 0 {
name = args[0]
}
// name := ctx.Args.String("name")
@@ -80,3 +113,151 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
}
}
func shouldRunGenerateProfilesNewForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateProfilesNewForm(cmd *cobra.Command, result *forms.GenerateProfilesNewFormResult) (string, error) {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return "", err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return "", err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
return "", errors.New("profile name required")
}
implantName := strings.TrimSpace(result.Name)
if implantName != "" {
if err := cmd.Flags().Set("name", implantName); err != nil {
return "", err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return "", err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return "", err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return "", err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return "", err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return "", err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return "", err
}
default:
return "", errors.New("unsupported C2 transport selection")
}
return profileName, nil
}
func shouldRunGenerateProfilesNewBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateProfilesNewBeaconForm(cmd *cobra.Command, result *forms.GenerateProfilesNewBeaconFormResult) (string, error) {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return "", err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return "", err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
return "", errors.New("profile name required")
}
implantName := strings.TrimSpace(result.Name)
if implantName != "" {
if err := cmd.Flags().Set("name", implantName); err != nil {
return "", err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return "", err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return "", err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return "", err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return "", err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return "", err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return "", err
}
default:
return "", errors.New("unsupported C2 transport selection")
}
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
return "", err
}
return profileName, nil
}
+2 -3
View File
@@ -22,8 +22,8 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func ProfilesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' profile?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' profile?", name), &confirm)
if !confirm {
return
}
+3 -10
View File
@@ -26,10 +26,10 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -140,10 +140,7 @@ func TrafficEncodersAddCmd(cmd *cobra.Command, con *console.SliverClient, args [
// saveFailedSample - Save the sample the encoder failed to properly encode/decode.
func saveFailedSample(encoderName string, test *clientpb.TrafficEncoderTest) {
confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName),
}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName), &confirm)
if !confirm {
return
}
@@ -286,10 +283,6 @@ func SelectTrafficEncoder(con *console.SliverClient) string {
}
sort.Strings(encoderNames)
var selectedEncoder string
prompt := &survey.Select{
Message: "Select a traffic encoder:",
Options: encoderNames,
}
survey.AskOne(prompt, &selectedEncoder)
_ = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
return selectedEncoder
}
+2 -6
View File
@@ -25,8 +25,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -86,12 +86,8 @@ func SelectHostIOC(host *clientpb.Host, con *console.SliverClient) (*clientpb.IO
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select an IOC:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select an IOC:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+2 -6
View File
@@ -28,9 +28,9 @@ import (
"text/tabwriter"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
@@ -172,12 +172,8 @@ func SelectHost(con *console.SliverClient) (*clientpb.Host, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a host:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a host:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+3 -3
View File
@@ -22,11 +22,11 @@ import (
"context"
"errors"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -39,7 +39,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
confirm := false
con.PrintWarnf("WARNING: This will kill the remote implant process\n\n")
if session != nil {
survey.AskOne(&survey.Confirm{Message: "Kill the active session?"}, &confirm, nil)
_ = forms.Confirm("Kill the active session?", &confirm)
if !confirm {
return
}
@@ -52,7 +52,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.ActiveTarget.Background()
return
} else if beacon != nil {
survey.AskOne(&survey.Confirm{Message: "Kill the active beacon?"}, &confirm, nil)
_ = forms.Confirm("Kill the active beacon?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -31,9 +31,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -106,11 +106,7 @@ func SelectLoot(cmd *cobra.Command, rpc rpcpb.SliverRPCClient) (*clientpb.Loot,
}
selected := ""
prompt := &survey.Select{
Message: "Select a piece of loot:",
Options: options,
}
err = survey.AskOne(prompt, &selected)
err = forms.Select("Select a piece of loot:", options, &selected)
if err != nil {
return nil, err
}
+2 -3
View File
@@ -26,12 +26,12 @@ import (
"strings"
"unicode/utf8"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -111,8 +111,7 @@ func saveLootToDisk(cmd *cobra.Command, loot *clientpb.Loot) (string, error) {
}
if _, err := os.Stat(saveTo); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
_ = forms.Confirm("Overwrite local file?", &overwrite)
if !overwrite {
return "", nil
}
+2 -3
View File
@@ -21,10 +21,10 @@ package loot
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
@@ -37,8 +37,7 @@ func LootRenameCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
}
oldName := loot.Name
newName := ""
prompt := &survey.Input{Message: "Enter new name: "}
survey.AskOne(prompt, &newName)
_ = forms.Input("Enter new name:", &newName)
loot, err = con.Rpc.LootUpdate(context.Background(), &clientpb.Loot{
ID: loot.ID,
+76
View File
@@ -0,0 +1,76 @@
package mcp
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the `mcp` command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
defaultConfig := clientmcp.DefaultConfig()
mcpCmd := &cobra.Command{
Use: consts.MCPStr,
Short: "Manage the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr}),
GroupID: consts.NetworkHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
McpCmd(cmd, con, args)
},
}
startCmd := &cobra.Command{
Use: consts.StartStr,
Short: "Start the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr, consts.StartStr}),
Run: func(cmd *cobra.Command, args []string) {
McpStartCmd(cmd, con, args)
},
}
flags.Bind("", false, startCmd, func(f *pflag.FlagSet) {
f.String("transport", string(defaultConfig.Transport), "mcp transport (http or sse)")
f.String("listen", defaultConfig.ListenAddress, "listen address for the MCP server")
f.String("name", defaultConfig.ServerName, "server name for MCP initialize")
f.String("version", defaultConfig.ServerVersion, "server version for MCP initialize")
})
flags.BindFlagCompletions(startCmd, func(comp *carapace.ActionMap) {
(*comp)["transport"] = carapace.ActionValues("http", "sse")
})
mcpCmd.AddCommand(startCmd)
stopCmd := &cobra.Command{
Use: consts.StopStr,
Short: "Stop the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr, consts.StopStr}),
Run: func(cmd *cobra.Command, args []string) {
McpStopCmd(cmd, con, args)
},
}
mcpCmd.AddCommand(stopCmd)
consoleCmd := &cobra.Command{
Use: "console",
Aliases: []string{"repl"},
Short: "Open an MCP console session",
Long: help.GetHelpFor([]string{consts.MCPStr, "console"}),
Run: func(cmd *cobra.Command, args []string) {
McpConsoleCmd(cmd, con, args)
},
}
flags.Bind("", false, consoleCmd, func(f *pflag.FlagSet) {
f.String("transport", string(defaultConfig.Transport), "mcp transport (http or sse)")
f.String("url", "", "mcp server URL (defaults to local server settings)")
})
flags.BindFlagCompletions(consoleCmd, func(comp *carapace.ActionMap) {
(*comp)["transport"] = carapace.ActionValues("http", "sse")
})
mcpCmd.AddCommand(consoleCmd)
return []*cobra.Command{mcpCmd}
}
+132
View File
@@ -0,0 +1,132 @@
package mcp
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/bishopfox/sliver/client/version"
mcpclient "github.com/mark3labs/mcp-go/client"
mcpapi "github.com/mark3labs/mcp-go/mcp"
"github.com/spf13/cobra"
)
// McpConsoleCmd connects to an MCP server and starts a minimal console.
func McpConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
rawTransport, _ := cmd.Flags().GetString("transport")
transport, err := clientmcp.ParseTransport(rawTransport)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
targetURL, _ := cmd.Flags().GetString("url")
if targetURL == "" {
cfg := clientmcp.GetStatus().Config.WithDefaults()
cfg.Transport = transport
targetURL, err = cfg.EndpointURL()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
client, err := newConsoleClient(transport, targetURL)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
defer client.Close()
ctx := context.Background()
if err := client.Start(ctx); err != nil {
con.PrintErrorf("%s\n", err)
return
}
_, err = client.Initialize(ctx, mcpapi.InitializeRequest{
Params: mcpapi.InitializeParams{
ProtocolVersion: mcpapi.LATEST_PROTOCOL_VERSION,
ClientInfo: mcpapi.Implementation{
Name: "sliver-mcp-console",
Version: version.Version,
},
Capabilities: mcpapi.ClientCapabilities{},
},
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Connected to MCP server (%s)\n", targetURL)
runConsoleREPL(ctx, client, cmd.InOrStdin(), cmd.OutOrStdout())
}
func newConsoleClient(transport clientmcp.Transport, targetURL string) (*mcpclient.Client, error) {
switch transport {
case clientmcp.TransportHTTP:
return mcpclient.NewStreamableHttpClient(targetURL)
case clientmcp.TransportSSE:
return mcpclient.NewSSEMCPClient(targetURL)
default:
return nil, fmt.Errorf("unsupported transport %q", transport)
}
}
func runConsoleREPL(ctx context.Context, client *mcpclient.Client, in io.Reader, out io.Writer) {
reader := bufio.NewReader(in)
for {
fmt.Fprint(out, "mcp> ")
line, err := reader.ReadString('\n')
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Fprintln(out)
return
}
fmt.Fprintf(out, "read error: %s\n", err)
return
}
line = strings.TrimSpace(line)
switch line {
case "":
continue
case "exit", "quit":
return
case "help", "?":
printConsoleHelp(out)
case "tools":
printConsoleTools(ctx, client, out)
default:
fmt.Fprintf(out, "unknown command: %s\n", line)
}
}
}
func printConsoleHelp(out io.Writer) {
fmt.Fprintln(out, "commands: tools, help, exit")
}
func printConsoleTools(ctx context.Context, client *mcpclient.Client, out io.Writer) {
tools, err := client.ListTools(ctx, mcpapi.ListToolsRequest{})
if err != nil {
fmt.Fprintf(out, "tools error: %s\n", err)
return
}
if len(tools.Tools) == 0 {
fmt.Fprintln(out, "no tools available")
return
}
for _, tool := range tools.Tools {
if tool.Description != "" {
fmt.Fprintf(out, "- %s: %s\n", tool.Name, tool.Description)
continue
}
fmt.Fprintf(out, "- %s\n", tool.Name)
}
}
+37
View File
@@ -0,0 +1,37 @@
package mcp
import (
"time"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
// McpCmd prints the current MCP server state.
func McpCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
status := clientmcp.GetStatus()
state := "stopped"
if status.Running {
state = "running"
}
con.Printf("Status: %s\n", state)
con.Printf("Transport: %s\n", status.Config.Transport)
con.Printf("Listen: %s\n", status.Config.ListenAddress)
endpoint, err := status.Config.EndpointURL()
if err == nil {
con.Printf("Endpoint: %s\n", endpoint)
} else {
con.PrintErrorf("Endpoint: %s\n", err)
}
if status.Running && !status.StartedAt.IsZero() {
uptime := time.Since(status.StartedAt).Truncate(time.Second)
con.Printf("Uptime: %s\n", uptime)
}
if status.LastError != "" {
con.PrintErrorf("Last error: %s\n", status.LastError)
}
}
+39
View File
@@ -0,0 +1,39 @@
package mcp
import (
"github.com/bishopfox/sliver/client/console"
slivermcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
// McpStartCmd starts the local MCP server.
func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
rawTransport, _ := cmd.Flags().GetString("transport")
transport, err := slivermcp.ParseTransport(rawTransport)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
listen, _ := cmd.Flags().GetString("listen")
name, _ := cmd.Flags().GetString("name")
version, _ := cmd.Flags().GetString("version")
cfg := slivermcp.Config{
Transport: transport,
ListenAddress: listen,
ServerName: name,
ServerVersion: version,
}.WithDefaults()
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Starting MCP server (%s) on %s\n", cfg.Transport, cfg.ListenAddress)
endpoint, err := cfg.EndpointURL()
if err == nil {
con.PrintInfof("Endpoint: %s\n", endpoint)
}
}
+24
View File
@@ -0,0 +1,24 @@
package mcp
import (
"context"
"time"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
const mcpStopTimeout = 5 * time.Second
// McpStopCmd stops the local MCP server.
func McpStopCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), mcpStopTimeout)
defer cancel()
if err := clientmcp.Stop(ctx); err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("MCP server stopped\n")
}
+2 -6
View File
@@ -22,9 +22,9 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
@@ -121,11 +121,7 @@ func selectWatchtowerConfig(configs *clientpb.MonitoringProviders) (*clientpb.Mo
}
selected := ""
prompt := &survey.Select{
Message: "Select a configuration:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a configuration:", options, &selected)
if err != nil {
return nil, err
}
+2 -6
View File
@@ -27,8 +27,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/rsteube/carapace"
)
@@ -49,11 +49,7 @@ func SelectPivotListener(listeners []*sliverpb.PivotListener, con *console.Slive
}
selected := ""
prompt := &survey.Select{
Message: "Select a beacon task:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a beacon task:", options, &selected)
if err != nil {
return nil, err
}
+2 -3
View File
@@ -21,8 +21,8 @@ package reaction
import (
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -33,8 +33,7 @@ func ReactionReloadCmd(cmd *cobra.Command, con *console.SliverClient, args []str
return
}
confirm := false
prompt := &survey.Confirm{Message: "Reload reactions from disk?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Reload reactions from disk?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -22,9 +22,9 @@ import (
"fmt"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -33,8 +33,7 @@ func ReactionSaveCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
reactionPath := GetReactionFilePath()
if _, err := os.Stat(reactionPath); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Overwrite reactions (%s) on disk?", reactionPath)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Overwrite reactions (%s) on disk?", reactionPath), &confirm)
if !confirm {
return
}
+3 -10
View File
@@ -22,9 +22,9 @@ import (
"errors"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -77,12 +77,8 @@ func getEventType(cmd *cobra.Command, con *console.SliverClient) (string, error)
}
func selectEventType(con *console.SliverClient) (string, error) {
prompt := &survey.Select{
Message: "Select an event:",
Options: core.ReactableEvents,
}
selection := ""
err := survey.AskOne(prompt, &selection)
err := forms.Select("Select an event:", core.ReactableEvents, &selection)
if err != nil {
return "", err
}
@@ -96,9 +92,6 @@ func selectEventType(con *console.SliverClient) (string, error) {
func userCommands() (string, error) {
text := ""
prompt := &survey.Multiline{
Message: "Enter commands: ",
}
err := survey.AskOne(prompt, &text)
err := forms.Text("Enter commands:", &text)
return text, err
}
+2 -7
View File
@@ -25,9 +25,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -65,13 +65,8 @@ func selectReaction(con *console.SliverClient) (*core.Reaction, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove blank line at the end
// Prompt user with options
prompt := &survey.Select{
Message: "Select a reaction:",
Options: options,
}
selection := ""
err := survey.AskOne(prompt, &selection)
err := forms.Select("Select a reaction:", options, &selection)
if err != nil {
return nil, err
}
+2
View File
@@ -39,6 +39,7 @@ import (
"github.com/bishopfox/sliver/client/command/jobs"
"github.com/bishopfox/sliver/client/command/licenses"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/command/mcp"
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/operators"
"github.com/bishopfox/sliver/client/command/reaction"
@@ -106,6 +107,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// C2 Network
bind(consts.NetworkHelpGroup,
jobs.Commands,
mcp.Commands,
websites.Commands,
wireguard.Commands,
c2profiles.Commands,
+2 -6
View File
@@ -27,8 +27,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -85,12 +85,8 @@ func SelectSession(onlyAlive bool, con *console.SliverClient) (*clientpb.Session
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a session:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a session:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+1
View File
@@ -19,6 +19,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
},
GroupID: consts.GenericHelpGroup,
}
settingsCmd.Flags().Bool("show", false, "Show current settings without prompting")
settingsCmd.AddCommand(&cobra.Command{
Use: consts.SaveStr,
Short: "Save the current settings to disk",
+2 -3
View File
@@ -19,9 +19,9 @@ package settings
*/
import (
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func IsUserAnAdult(con *console.SliverClient) bool {
return true
}
confirm := false
prompt := &survey.Confirm{Message: "This action is bad OPSEC, are you an adult?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
return confirm
}
+67 -8
View File
@@ -19,11 +19,13 @@ package settings
*/
import (
"errors"
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
@@ -39,6 +41,25 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
if shouldRunSettingsForm(cmd, con, args) {
tableStyleOptions := make([]string, 0, len(tableStyles))
for option := range tableStyles {
tableStyleOptions = append(tableStyleOptions, option)
}
result, err := forms.SettingsForm(con.Settings, tableStyleOptions)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Settings form failed: %s\n", err)
return
}
if err := applySettingsForm(con.Settings, result); err != nil {
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
return
}
}
tw := table.NewWriter()
tw.SetStyle(GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
@@ -48,6 +69,7 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
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"})
tw.AppendRow(table.Row{"Vim Mode", con.Settings.VimMode, "Navigation mode, vim style"})
tw.AppendRow(table.Row{"User Connect", con.Settings.UserConnect, "Show operator connect/disconnect events"})
tw.AppendRow(table.Row{"Console Logs", con.Settings.ConsoleLogs, "Log console output to disk"})
con.Printf("%s\n", tw.Render())
}
@@ -91,8 +113,7 @@ func SettingsSmallTerm(cmd *cobra.Command, con *console.SliverClient, args []str
}
}
result := ""
prompt := &survey.Input{Message: "Set small width:"}
err = survey.AskOne(prompt, &result)
err = forms.Input("Set small width:", &result)
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -126,11 +147,7 @@ func SettingsTablesCmd(cmd *cobra.Command, con *console.SliverClient, args []str
options = append(options, option)
}
style := ""
prompt := &survey.Select{
Message: "Choose a style:",
Options: options,
}
err = survey.AskOne(prompt, &style)
err = forms.Select("Choose a style:", options, &style)
if err != nil {
con.PrintErrorf("No selection\n")
return
@@ -173,3 +190,45 @@ func SettingsUserConnect(cmd *cobra.Command, con *console.SliverClient, args []s
con.Settings.UserConnect = !con.Settings.UserConnect
con.PrintInfof("User connect events = %v\n", con.Settings.UserConnect)
}
func shouldRunSettingsForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
if show, err := cmd.Flags().GetBool("show"); err == nil && show {
return false
}
return cmd.Flags().NFlag() == 0
}
func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFormResult) error {
if settings == nil {
return errors.New("settings are required")
}
if result == nil {
return errors.New("settings form result is required")
}
if _, ok := tableStyles[result.TableStyle]; !ok {
return errors.New("invalid table style")
}
width, err := strconv.Atoi(strings.TrimSpace(result.SmallTermWidth))
if err != nil {
return err
}
if width < 1 {
return errors.New("small terminal width must be 1 or greater")
}
settings.TableStyle = result.TableStyle
settings.AutoAdult = result.AutoAdult
settings.BeaconAutoResults = result.BeaconAutoResults
settings.SmallTermWidth = width
settings.AlwaysOverflow = result.AlwaysOverflow
settings.VimMode = result.VimMode
settings.UserConnect = result.UserConnect
settings.ConsoleLogs = result.ConsoleLogs
return nil
}
+2 -5
View File
@@ -22,9 +22,9 @@ import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"golang.org/x/term"
@@ -212,10 +212,7 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
if 1 < len(pages) {
if pageNumber+1 < len(pages) {
nextPage := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("[%d/%d] Continue?", pageNumber+1, len(pages)),
}
survey.AskOne(prompt, &nextPage)
_ = forms.Confirm(fmt.Sprintf("[%d/%d] Continue?", pageNumber+1, len(pages)), &nextPage)
if !nextPage {
break
}
+2 -3
View File
@@ -27,8 +27,8 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
"gopkg.in/AlecAivazis/survey.v1"
)
// SocksStartCmd - Add a new tunneled port forward.
@@ -56,8 +56,7 @@ func SocksStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
con.PrintWarnf("SOCKS proxy authentication credentials are tunneled to the implant\n")
con.PrintWarnf("These credentials are recoverable from the implant's memory!\n\n")
confirm := false
prompt := &survey.Confirm{Message: "Do you understand the implication?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you understand the implication?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -29,10 +29,10 @@ import (
"github.com/spf13/cobra"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -214,12 +214,8 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.MultiSelect{
Message: "Select sessions and beacons:",
Options: options,
}
selected := []string{}
survey.AskOne(prompt, &selected)
_ = forms.MultiSelect("Select sessions and beacons:", options, &selected)
if len(selected) == 0 {
return nil, nil, fmt.Errorf("no sessions or beacons selected 🤔")
+4 -10
View File
@@ -24,7 +24,6 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
"github.com/bishopfox/sliver/client/command/extensions"
@@ -36,6 +35,7 @@ import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
@@ -750,11 +750,7 @@ func taskResponseDownload(download *sliverpb.Download, con *console.SliverClient
saveTo = "Save to File ..."
)
action := saveTo
prompt := &survey.Select{
Message: "Choose an option:",
Options: []string{dump, saveTo},
}
err := survey.AskOne(prompt, &action, survey.WithValidator(survey.Required))
err := forms.SelectRequired("Choose an option:", []string{dump, saveTo}, &action)
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -769,16 +765,14 @@ func taskResponseDownload(download *sliverpb.Download, con *console.SliverClient
func promptSaveToFile(data []byte, con *console.SliverClient) {
saveTo := ""
saveToPrompt := &survey.Input{Message: "Save to: "}
err := survey.AskOne(saveToPrompt, &saveTo)
err := forms.Input("Save to:", &saveTo)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if _, err := os.Stat(saveTo); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite existing file?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -9,8 +9,8 @@ import (
"text/tabwriter"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/rsteube/carapace"
)
@@ -32,11 +32,7 @@ func SelectBeaconTask(tasks []*clientpb.BeaconTask) (*clientpb.BeaconTask, error
}
selected := ""
prompt := &survey.Select{
Message: "Select a beacon task:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a beacon task:", options, &selected)
if err != nil {
return nil, err
}
+6 -12
View File
@@ -28,16 +28,15 @@ import (
"net/url"
"os"
"os/user"
"path"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -57,14 +56,12 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Println()
con.Println(console.Warn + "You're trying to update over an insecure connection, this is a really bad idea!")
confirm := false
prompt := &survey.Confirm{Message: "Recklessly update?"}
survey.AskOne(prompt, &confirm)
forms.Confirm("Recklessly update?", &confirm)
if !confirm {
return
}
confirm = false
prompt = &survey.Confirm{Message: "Seriously?"}
survey.AskOne(prompt, &confirm)
forms.Confirm("Seriously?", &confirm)
if !confirm {
return
}
@@ -115,9 +112,9 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("No new releases.\n")
}
now := time.Now()
lastCheck := []byte(fmt.Sprintf("%d", now.Unix()))
lastCheck := fmt.Appendf(nil, "%d", now.Unix())
appDir := assets.GetRootAppDir()
lastUpdateCheckPath := path.Join(appDir, consts.LastUpdateCheckFileName)
lastUpdateCheckPath := filepath.Join(appDir, consts.LastUpdateCheckFileName)
err = os.WriteFile(lastUpdateCheckPath, lastCheck, 0o600)
if err != nil {
con.Printf("Failed to save update check time %s", err)
@@ -231,10 +228,7 @@ func updateAvailable(con *console.SliverClient, client *http.Client, release *ve
con.Println()
confirm := false
prompt := &survey.Confirm{
Message: "Download update?",
}
survey.AskOne(prompt, &confirm)
forms.Confirm("Download update?", &confirm)
if confirm {
con.Printf("Please wait ...")
err := downloadAsset(client, serverAsset, saveTo)
+3 -8
View File
@@ -27,12 +27,12 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/beacons"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -74,8 +74,7 @@ func SessionOrBeaconByID(id string, con *console.SliverClient) (*clientpb.Sessio
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, nil, err
}
if err == nil {
} else {
for _, session := range sessions.Sessions {
if strings.HasPrefix(session.ID, id) {
return session, nil, nil
@@ -162,12 +161,8 @@ func SelectSessionOrBeacon(con *console.SliverClient) (*clientpb.Session, *clien
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a session or beacon:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a session or beacon:", options, &selected)
if selected == "" {
return nil, nil, ErrNoSelection
}
@@ -27,8 +27,8 @@ import (
"path"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -130,8 +130,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
func confirmAddDirectory() bool {
confirm := false
prompt := &survey.Confirm{Message: "Recursively add entire directory?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Recursively add entire directory?", &confirm)
return confirm
}
+1
View File
@@ -276,6 +276,7 @@ const (
WgPortFwdStr = "wg-portfwd"
MonitorStr = "monitor"
MonitorConfigStr = "config"
MCPStr = "mcp"
SSHStr = "ssh"
DLLHijackStr = "dllhijack"
InteractiveStr = "interactive"
+52
View File
@@ -0,0 +1,52 @@
package forms
import (
"errors"
"github.com/charmbracelet/huh"
)
// ArmoryInstallOption represents an armory install option.
type ArmoryInstallOption struct {
Value string
Label string
}
// ArmoryInstallFormResult captures the selection from the armory install form.
type ArmoryInstallFormResult struct {
SelectedNames []string
}
// ArmoryInstallForm prompts for armory packages or bundles to install.
func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult, error) {
if len(options) == 0 {
return nil, errors.New("armory install options are required")
}
selectOptions := make([]huh.Option[string], 0, len(options))
for _, option := range options {
if option.Value == "" {
return nil, errors.New("armory install option value is required")
}
if option.Label == "" {
return nil, errors.New("armory install option label is required")
}
selectOptions = append(selectOptions, huh.NewOption(option.Label, option.Value))
}
result := &ArmoryInstallFormResult{}
field := huh.NewMultiSelect[string]().
Title("Select packages or bundles to install").
Description("Use space to select and enter to install.").
Options(selectOptions...).
Height(listHeight(len(selectOptions))).
Value(&result.SelectedNames)
form := huh.NewForm(huh.NewGroup(field))
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+51
View File
@@ -0,0 +1,51 @@
package forms
import (
"errors"
"github.com/charmbracelet/huh"
)
// ArmoryUpdateOption represents an update option in the armory update form.
type ArmoryUpdateOption struct {
ID string
Label string
}
// ArmoryUpdateFormResult captures the selection from the armory update form.
type ArmoryUpdateFormResult struct {
SelectedIDs []string
}
// ArmoryUpdateForm prompts for armory updates to apply.
func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, error) {
if len(options) == 0 {
return nil, errors.New("armory update options are required")
}
selectOptions := make([]huh.Option[string], 0, len(options))
for _, option := range options {
if option.ID == "" {
return nil, errors.New("armory update option id is required")
}
if option.Label == "" {
return nil, errors.New("armory update option label is required")
}
selectOptions = append(selectOptions, huh.NewOption(option.Label, option.ID))
}
result := &ArmoryUpdateFormResult{}
field := huh.NewMultiSelect[string]().
Title("Select updates to apply").
Description("Use space to select and enter to apply.").
Options(selectOptions...).
Height(listHeight(len(selectOptions))).
Value(&result.SelectedIDs)
form := huh.NewForm(huh.NewGroup(field))
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+136
View File
@@ -0,0 +1,136 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// ErrUserAborted exposes the huh user-abort sentinel for callers.
var ErrUserAborted = huh.ErrUserAborted
// GenerateFormResult captures the inputs needed to drive the generate command.
type GenerateFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
}
// GenerateForm prompts for core generate flags and returns the collected values.
func GenerateForm() (*GenerateFormResult, error) {
result := &GenerateFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
huh.NewInput().
Title("Save path (optional)").
Description("File or directory; defaults to the current directory").
Value(&result.Save),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+181
View File
@@ -0,0 +1,181 @@
package forms
import (
"errors"
"strconv"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateBeaconFormResult captures the inputs needed to drive the generate beacon command.
type GenerateBeaconFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
Days string
Hours string
Minutes string
Seconds string
Jitter string
}
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
result := &GenerateBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
huh.NewInput().
Title("Save path (optional)").
Description("File or directory; defaults to the current directory").
Value(&result.Save),
),
huh.NewGroup(
huh.NewInput().
Title("Beacon interval days (optional)").
Description("Leave blank for 0").
Value(&result.Days).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval hours (optional)").
Description("Leave blank for 0").
Value(&result.Hours).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval minutes (optional)").
Description("Leave blank for 0").
Value(&result.Minutes).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval seconds (optional)").
Description("Leave blank for default 60 seconds unless other units are set").
Value(&result.Seconds).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon jitter seconds (optional)").
Description("Leave blank for default 30 seconds").
Value(&result.Jitter).
Validate(validateOptionalNonNegativeInt),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
func validateOptionalNonNegativeInt(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return nil
}
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return errors.New("must be a whole number")
}
if parsed < 0 {
return errors.New("must be 0 or greater")
}
return nil
}
+139
View File
@@ -0,0 +1,139 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateProfilesNewFormResult captures the inputs needed to drive the profiles new command.
type GenerateProfilesNewFormResult struct {
ProfileName string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
}
// GenerateProfilesNewForm prompts for profiles new flags and returns the collected values.
func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
result := &GenerateProfilesNewFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Profile name").
Value(&result.ProfileName).
Validate(func(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("profile name required")
}
return util.AllowedName(trimmed)
}),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
@@ -0,0 +1,171 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateProfilesNewBeaconFormResult captures the inputs needed to drive the profiles new beacon command.
type GenerateProfilesNewBeaconFormResult struct {
ProfileName string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Days string
Hours string
Minutes string
Seconds string
Jitter string
}
// GenerateProfilesNewBeaconForm prompts for profiles new beacon flags and returns the collected values.
func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, error) {
result := &GenerateProfilesNewBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Profile name").
Value(&result.ProfileName).
Validate(func(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("profile name required")
}
return util.AllowedName(trimmed)
}),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
),
huh.NewGroup(
huh.NewInput().
Title("Beacon interval days (optional)").
Description("Leave blank for 0").
Value(&result.Days).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval hours (optional)").
Description("Leave blank for 0").
Value(&result.Hours).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval minutes (optional)").
Description("Leave blank for 0").
Value(&result.Minutes).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval seconds (optional)").
Description("Leave blank for default 60 seconds unless other units are set").
Value(&result.Seconds).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon jitter seconds (optional)").
Description("Leave blank for default 30 seconds").
Value(&result.Jitter).
Validate(validateOptionalNonNegativeInt),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+154
View File
@@ -0,0 +1,154 @@
package forms
import (
"errors"
"strings"
"github.com/charmbracelet/huh"
)
const defaultSelectHeight = 10
// Confirm prompts for a yes/no answer.
func Confirm(title string, value *bool) error {
if value == nil {
return errors.New("confirm value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(title).
Value(value),
),
)
return form.Run()
}
// Input prompts for a single-line string.
func Input(title string, value *string) error {
if value == nil {
return errors.New("input value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title(title).
Value(value),
),
)
return form.Run()
}
// Text prompts for multi-line input.
func Text(title string, value *string) error {
if value == nil {
return errors.New("text value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title(title).
ExternalEditor(false).
Value(value),
),
)
return form.Run()
}
// Select prompts for a single selection from options.
func Select(title string, options []string, value *string) error {
return selectPrompt(title, options, value, false)
}
// SelectRequired prompts for a single selection and requires a non-empty value.
func SelectRequired(title string, options []string, value *string) error {
return selectPrompt(title, options, value, true)
}
// MultiSelect prompts for selecting multiple values.
func MultiSelect(title string, options []string, value *[]string) error {
if value == nil {
return errors.New("multi-select value is required")
}
if len(options) == 0 {
return errors.New("multi-select options are required")
}
field := huh.NewMultiSelect[string]().
Title(title).
Options(makeStringOptions(options)...).
Height(listHeight(len(options))).
Value(value)
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
}
func selectPrompt(title string, options []string, value *string, required bool) error {
if value == nil {
return errors.New("select value is required")
}
if len(options) == 0 {
return errors.New("select options are required")
}
ensureSelectedValue(options, value)
field := huh.NewSelect[string]().
Title(title).
Options(makeStringOptions(options)...).
Height(listHeight(len(options))).
Value(value)
if required {
field.Validate(func(val string) error {
if strings.TrimSpace(val) == "" {
return errors.New("selection required")
}
return nil
})
}
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
}
func ensureSelectedValue(options []string, value *string) {
if value == nil || len(options) == 0 {
return
}
if *value == "" {
*value = options[0]
return
}
for _, option := range options {
if option == *value {
return
}
}
*value = options[0]
}
func makeStringOptions(options []string) []huh.Option[string] {
converted := make([]huh.Option[string], 0, len(options))
for _, option := range options {
converted = append(converted, huh.NewOption(option, option))
}
return converted
}
func listHeight(count int) int {
if count < 1 {
return 1
}
if count > defaultSelectHeight {
return defaultSelectHeight
}
return count
}
+113
View File
@@ -0,0 +1,113 @@
package forms
import (
"errors"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/charmbracelet/huh"
)
// SettingsFormResult captures the inputs needed to update client settings.
type SettingsFormResult struct {
TableStyle string
AutoAdult bool
BeaconAutoResults bool
SmallTermWidth string
AlwaysOverflow bool
VimMode bool
UserConnect bool
ConsoleLogs bool
}
// SettingsForm prompts for client settings and returns the collected values.
func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (*SettingsFormResult, error) {
if settings == nil {
return nil, errors.New("settings are required")
}
if len(tableStyleOptions) == 0 {
return nil, errors.New("no table styles available")
}
result := &SettingsFormResult{
TableStyle: settings.TableStyle,
AutoAdult: settings.AutoAdult,
BeaconAutoResults: settings.BeaconAutoResults,
SmallTermWidth: strconv.Itoa(settings.SmallTermWidth),
AlwaysOverflow: settings.AlwaysOverflow,
VimMode: settings.VimMode,
UserConnect: settings.UserConnect,
ConsoleLogs: settings.ConsoleLogs,
}
if result.TableStyle == "" {
result.TableStyle = tableStyleOptions[0]
}
styleOptions := make([]huh.Option[string], 0, len(tableStyleOptions))
for _, option := range tableStyleOptions {
styleOptions = append(styleOptions, huh.NewOption(option, option))
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Table style").
Options(styleOptions...).
Height(len(styleOptions)).
Value(&result.TableStyle),
huh.NewInput().
Title("Small terminal width").
Description("Omit some table columns when the terminal is narrower than this value").
Value(&result.SmallTermWidth).
Validate(validateRequiredPositiveInt),
huh.NewConfirm().
Title("Always overflow tables").
Description("Disable table pagination").
Value(&result.AlwaysOverflow),
).Title("Display"),
huh.NewGroup(
huh.NewConfirm().
Title("Auto accept OPSEC warnings").
Description("Skip confirmation prompts for risky actions").
Value(&result.AutoAdult),
huh.NewConfirm().
Title("Auto display beacon results").
Description("Show beacon task results when tasks complete").
Value(&result.BeaconAutoResults),
huh.NewConfirm().
Title("Vim navigation mode").
Description("Use vim-style navigation in the console").
Value(&result.VimMode),
huh.NewConfirm().
Title("User connect events").
Description("Show operator connect and disconnect events").
Value(&result.UserConnect),
huh.NewConfirm().
Title("Console logs").
Description("Log console output to disk").
Value(&result.ConsoleLogs),
).Title("Behavior"),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
func validateRequiredPositiveInt(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("value required")
}
parsed, err := strconv.Atoi(trimmed)
if err != nil {
return errors.New("must be a whole number")
}
if parsed < 1 {
return errors.New("must be 1 or greater")
}
return nil
}
+118
View File
@@ -0,0 +1,118 @@
package mcp
import (
"fmt"
"net"
"net/url"
"strings"
"github.com/bishopfox/sliver/client/version"
)
// Transport identifies the MCP server transport to expose.
type Transport string
const (
TransportHTTP Transport = "http"
TransportSSE Transport = "sse"
)
// Config controls the local MCP server settings.
type Config struct {
Transport Transport
ListenAddress string
ServerName string
ServerVersion string
}
// DefaultConfig returns the baseline MCP configuration.
func DefaultConfig() Config {
return Config{
Transport: TransportSSE,
ListenAddress: "127.0.0.1:8080",
ServerName: "Sliver MCP",
ServerVersion: version.Version,
}
}
// WithDefaults fills any empty fields with defaults.
func (c Config) WithDefaults() Config {
defaults := DefaultConfig()
if c.Transport == "" {
c.Transport = defaults.Transport
}
if c.ListenAddress == "" {
c.ListenAddress = defaults.ListenAddress
}
if c.ServerName == "" {
c.ServerName = defaults.ServerName
}
if c.ServerVersion == "" {
c.ServerVersion = defaults.ServerVersion
}
return c
}
// ParseTransport validates an input transport string.
func ParseTransport(raw string) (Transport, error) {
value := strings.ToLower(strings.TrimSpace(raw))
if value == "" {
return DefaultConfig().Transport, nil
}
switch Transport(value) {
case TransportHTTP, TransportSSE:
return Transport(value), nil
default:
return "", fmt.Errorf("unsupported transport %q", raw)
}
}
// Validate ensures the configuration can be used for a server start.
func (c Config) Validate() error {
if c.Transport != TransportHTTP && c.Transport != TransportSSE {
return fmt.Errorf("unsupported transport %q", c.Transport)
}
if c.ListenAddress == "" {
return fmt.Errorf("listen address is required")
}
if _, _, err := net.SplitHostPort(c.ListenAddress); err != nil {
return fmt.Errorf("invalid listen address %q: %w", c.ListenAddress, err)
}
if c.ServerName == "" {
return fmt.Errorf("server name is required")
}
if c.ServerVersion == "" {
return fmt.Errorf("server version is required")
}
return nil
}
// EndpointURL returns the base URL for clients to connect.
func (c Config) EndpointURL() (string, error) {
cfg := c.WithDefaults()
if err := cfg.Validate(); err != nil {
return "", err
}
host, port, err := net.SplitHostPort(cfg.ListenAddress)
if err != nil {
return "", err
}
if host == "" {
host = "127.0.0.1"
}
endpoint := url.URL{
Scheme: "http",
Host: net.JoinHostPort(host, port),
}
switch cfg.Transport {
case TransportHTTP:
endpoint.Path = "/mcp"
case TransportSSE:
endpoint.Path = "/sse"
default:
return "", fmt.Errorf("unsupported transport %q", cfg.Transport)
}
return endpoint.String(), nil
}
File diff suppressed because it is too large. Load diff
+67
View File
@@ -0,0 +1,67 @@
package mcp
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/bishopfox/sliver/client/assets"
)
const mcpLogTimeFormat = "2006-01-02_15-04-05"
func newMCPLogger() (*log.Logger, *os.File, error) {
logsDir := assets.GetMCPLogsDir()
logPath := filepath.Join(logsDir, fmt.Sprintf("%s.log", time.Now().Format(mcpLogTimeFormat)))
logFile, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
if err != nil {
return nil, nil, err
}
logger := log.New(logFile, "", log.LstdFlags|log.Lshortfile)
return logger, logFile, nil
}
func (m *mcpManager) logf(format string, args ...any) {
if m == nil {
return
}
m.mu.Lock()
logger := m.logger
m.mu.Unlock()
if logger != nil {
logger.Printf(format, args...)
}
}
func (s *SliverMCPServer) logf(format string, args ...any) {
if s == nil || s.logger == nil {
return
}
s.logger.Printf(format, args...)
}
func (s *SliverMCPServer) logToolCall(tool, sessionID, beaconID string, extras ...string) {
if s == nil || s.logger == nil {
return
}
parts := make([]string, 0, 2+len(extras))
if sessionID != "" {
parts = append(parts, fmt.Sprintf("session_id=%s", sessionID))
}
if beaconID != "" {
parts = append(parts, fmt.Sprintf("beacon_id=%s", beaconID))
}
for _, extra := range extras {
if extra != "" {
parts = append(parts, extra)
}
}
if len(parts) == 0 {
s.logger.Printf("mcp tool=%s", tool)
return
}
s.logger.Printf("mcp tool=%s %s", tool, strings.Join(parts, " "))
}
+200
View File
@@ -0,0 +1,200 @@
package mcp
import (
"context"
"errors"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/bishopfox/sliver/protobuf/rpcpb"
mcpserver "github.com/mark3labs/mcp-go/server"
)
var (
ErrAlreadyRunning = errors.New("mcp server already running")
ErrNotRunning = errors.New("mcp server not running")
)
// Status describes the running state of the MCP server.
type Status struct {
Running bool
StartedAt time.Time
Config Config
LastError string
}
type serverTransport interface {
Start(addr string) error
Shutdown(ctx context.Context) error
}
type mcpManager struct {
mu sync.Mutex
cfg Config
running bool
startedAt time.Time
lastErr string
server *SliverMCPServer
transport serverTransport
done chan struct{}
logger *log.Logger
logFile *os.File
}
func newManager() *mcpManager {
return &mcpManager{
cfg: DefaultConfig(),
}
}
var defaultManager = newManager()
// GetStatus returns the current MCP server state.
func GetStatus() Status {
return defaultManager.status()
}
// Start launches the MCP server using the provided configuration.
func Start(cfg Config, rpc rpcpb.SliverRPCClient) error {
return defaultManager.start(cfg, rpc)
}
// ServeStdio runs the MCP server over stdio using the provided configuration.
func ServeStdio(cfg Config, rpc rpcpb.SliverRPCClient) error {
cfg = cfg.WithDefaults()
if err := cfg.Validate(); err != nil {
return err
}
logger, logFile, err := newMCPLogger()
if err != nil {
return err
}
defer logFile.Close()
logger.Printf("starting mcp stdio server")
srv := newServer(cfg, rpc, logger)
err = mcpserver.ServeStdio(srv.server)
if err != nil {
logger.Printf("mcp stdio server error: %v", err)
} else {
logger.Printf("mcp stdio server stopped")
}
return err
}
// Stop shuts down the MCP server.
func Stop(ctx context.Context) error {
return defaultManager.stop(ctx)
}
func (m *mcpManager) status() Status {
m.mu.Lock()
defer m.mu.Unlock()
return Status{
Running: m.running,
StartedAt: m.startedAt,
Config: m.cfg,
LastError: m.lastErr,
}
}
func (m *mcpManager) start(cfg Config, rpc rpcpb.SliverRPCClient) error {
cfg = cfg.WithDefaults()
if err := cfg.Validate(); err != nil {
return err
}
m.mu.Lock()
if m.running {
m.mu.Unlock()
return ErrAlreadyRunning
}
logger, logFile, err := newMCPLogger()
if err != nil {
m.mu.Unlock()
return err
}
mcpServer := newServer(cfg, rpc, logger)
var transport serverTransport
switch cfg.Transport {
case TransportHTTP:
transport = mcpserver.NewStreamableHTTPServer(mcpServer.server)
case TransportSSE:
transport = mcpserver.NewSSEServer(mcpServer.server)
default:
m.mu.Unlock()
logFile.Close()
return errors.New("unsupported transport")
}
m.cfg = cfg
m.running = true
m.startedAt = time.Now()
m.lastErr = ""
m.server = mcpServer
m.transport = transport
m.done = make(chan struct{})
m.logger = logger
m.logFile = logFile
addr := cfg.ListenAddress
done := m.done
m.mu.Unlock()
logger.Printf("starting mcp server transport=%s listen=%s", cfg.Transport, cfg.ListenAddress)
go m.run(addr, transport, done)
return nil
}
func (m *mcpManager) run(addr string, transport serverTransport, done chan struct{}) {
err := transport.Start(addr)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
m.logf("mcp server error: %v", err)
m.mu.Lock()
m.lastErr = err.Error()
m.mu.Unlock()
}
m.logf("mcp server stopped")
m.mu.Lock()
m.running = false
m.transport = nil
m.server = nil
logFile := m.logFile
m.logFile = nil
m.logger = nil
m.mu.Unlock()
if logFile != nil {
logFile.Close()
}
close(done)
}
func (m *mcpManager) stop(ctx context.Context) error {
m.mu.Lock()
if !m.running {
m.mu.Unlock()
return ErrNotRunning
}
transport := m.transport
done := m.done
m.mu.Unlock()
if transport == nil {
return ErrNotRunning
}
m.logf("mcp server shutdown requested")
if err := transport.Shutdown(ctx); err != nil {
return err
}
if done == nil {
return nil
}
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
+247
View File
@@ -0,0 +1,247 @@
package mcp
import (
"context"
"log"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
mcpapi "github.com/mark3labs/mcp-go/mcp"
mcpserver "github.com/mark3labs/mcp-go/server"
)
const (
listSessionsAndBeaconsToolName = "list_sessions_and_beacons"
)
type sessionSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Transport string `json:"transport"`
RemoteAddress string `json:"remote_address"`
Hostname string `json:"hostname"`
Username string `json:"username"`
PID int32 `json:"pid"`
OS string `json:"os"`
Arch string `json:"arch"`
Locale string `json:"locale"`
LastCheckin int64 `json:"last_checkin"`
IsDead bool `json:"is_dead"`
Integrity string `json:"integrity"`
}
type beaconSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Transport string `json:"transport"`
RemoteAddress string `json:"remote_address"`
Hostname string `json:"hostname"`
Username string `json:"username"`
PID int32 `json:"pid"`
OS string `json:"os"`
Arch string `json:"arch"`
Locale string `json:"locale"`
LastCheckin int64 `json:"last_checkin"`
NextCheckin int64 `json:"next_checkin"`
Interval int64 `json:"interval"`
Jitter int64 `json:"jitter"`
TasksCount int64 `json:"tasks_count"`
TasksCountCompleted int64 `json:"tasks_count_completed"`
IsDead bool `json:"is_dead"`
Integrity string `json:"integrity"`
}
type listSessionsAndBeaconsResult struct {
Sessions []sessionSummary `json:"sessions"`
SessionsCount int `json:"sessions_count"`
Beacons []beaconSummary `json:"beacons"`
BeaconsCount int `json:"beacons_count"`
}
// SliverMCPServer wraps the MCP server with Sliver RPC access for handlers.
type SliverMCPServer struct {
Rpc rpcpb.SliverRPCClient
server *mcpserver.MCPServer
logger *log.Logger
}
func newServer(cfg Config, rpc rpcpb.SliverRPCClient, logger *log.Logger) *SliverMCPServer {
base := mcpserver.NewMCPServer(
cfg.ServerName,
cfg.ServerVersion,
mcpserver.WithToolCapabilities(false),
)
listSessionsAndBeaconsTool := mcpapi.NewTool(
listSessionsAndBeaconsToolName,
mcpapi.WithDescription("List active sessions and beacons."),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
lsTool := mcpapi.NewTool(
lsToolName,
mcpapi.WithDescription("List the contents of a remote directory."),
mcpapi.WithInputSchema[lsArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
cdTool := mcpapi.NewTool(
cdToolName,
mcpapi.WithDescription("Change the current directory on a remote session or beacon."),
mcpapi.WithInputSchema[cdArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(false),
)
catTool := mcpapi.NewTool(
catToolName,
mcpapi.WithDescription("Download and return the contents of a remote file."),
mcpapi.WithInputSchema[catArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
pwdTool := mcpapi.NewTool(
pwdToolName,
mcpapi.WithDescription("Return the current working directory of a remote session or beacon."),
mcpapi.WithInputSchema[pwdArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
rmTool := mcpapi.NewTool(
rmToolName,
mcpapi.WithDescription("Remove a file or directory on the remote target."),
mcpapi.WithInputSchema[rmArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
mvTool := mcpapi.NewTool(
mvToolName,
mcpapi.WithDescription("Move or rename a file or directory on the remote target."),
mcpapi.WithInputSchema[mvArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
cpTool := mcpapi.NewTool(
cpToolName,
mcpapi.WithDescription("Copy a file on the remote target."),
mcpapi.WithInputSchema[cpArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
mkdirTool := mcpapi.NewTool(
mkdirToolName,
mcpapi.WithDescription("Create a directory on the remote target."),
mcpapi.WithInputSchema[mkdirArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
chmodTool := mcpapi.NewTool(
chmodToolName,
mcpapi.WithDescription("Change file or directory permissions on the remote target."),
mcpapi.WithInputSchema[chmodArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
chownTool := mcpapi.NewTool(
chownToolName,
mcpapi.WithDescription("Change file or directory ownership on the remote target."),
mcpapi.WithInputSchema[chownArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
srv := &SliverMCPServer{
Rpc: rpc,
server: base,
logger: logger,
}
srv.server.AddTool(listSessionsAndBeaconsTool, srv.listSessionsAndBeaconsHandler)
srv.server.AddTool(lsTool, srv.lsHandler)
srv.server.AddTool(cdTool, srv.cdHandler)
srv.server.AddTool(catTool, srv.catHandler)
srv.server.AddTool(pwdTool, srv.pwdHandler)
srv.server.AddTool(rmTool, srv.rmHandler)
srv.server.AddTool(mvTool, srv.mvHandler)
srv.server.AddTool(cpTool, srv.cpHandler)
srv.server.AddTool(mkdirTool, srv.mkdirHandler)
srv.server.AddTool(chmodTool, srv.chmodHandler)
srv.server.AddTool(chownTool, srv.chownHandler)
return srv
}
func (s *SliverMCPServer) listSessionsAndBeaconsHandler(ctx context.Context, _ mcpapi.CallToolRequest) (*mcpapi.CallToolResult, error) {
if s.Rpc == nil {
return mcpapi.NewToolResultError("rpc client not configured"), nil
}
s.logToolCall(listSessionsAndBeaconsToolName, "", "")
sessionsResp, err := s.Rpc.GetSessions(ctx, &commonpb.Empty{})
if err != nil {
return mcpapi.NewToolResultErrorFromErr("failed to list sessions", err), nil
}
beaconsResp, err := s.Rpc.GetBeacons(ctx, &commonpb.Empty{})
if err != nil {
return mcpapi.NewToolResultErrorFromErr("failed to list beacons", err), nil
}
result := listSessionsAndBeaconsResult{
Sessions: make([]sessionSummary, 0, len(sessionsResp.GetSessions())),
Beacons: make([]beaconSummary, 0, len(beaconsResp.GetBeacons())),
}
for _, session := range sessionsResp.GetSessions() {
if session == nil {
continue
}
result.Sessions = append(result.Sessions, sessionSummary{
ID: session.ID,
Name: session.Name,
Transport: session.Transport,
RemoteAddress: session.RemoteAddress,
Hostname: session.Hostname,
Username: session.Username,
PID: session.PID,
OS: session.OS,
Arch: session.Arch,
Locale: session.Locale,
LastCheckin: session.LastCheckin,
IsDead: session.IsDead,
Integrity: session.Integrity,
})
}
for _, beacon := range beaconsResp.GetBeacons() {
if beacon == nil {
continue
}
result.Beacons = append(result.Beacons, beaconSummary{
ID: beacon.ID,
Name: beacon.Name,
Transport: beacon.Transport,
RemoteAddress: beacon.RemoteAddress,
Hostname: beacon.Hostname,
Username: beacon.Username,
PID: beacon.PID,
OS: beacon.OS,
Arch: beacon.Arch,
Locale: beacon.Locale,
LastCheckin: beacon.LastCheckin,
NextCheckin: beacon.NextCheckin,
Interval: beacon.Interval,
Jitter: beacon.Jitter,
TasksCount: beacon.TasksCount,
TasksCountCompleted: beacon.TasksCountCompleted,
IsDead: beacon.IsDead,
Integrity: beacon.Integrity,
})
}
result.SessionsCount = len(result.Sessions)
result.BeaconsCount = len(result.Beacons)
return mcpapi.NewToolResultStructuredOnly(result), nil
}
+105 -8
View File
@@ -19,15 +19,23 @@ package version
*/
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
const (
normal = "\033[0m"
bold = "\033[1m"
defaultVersion = "devel"
defaultReleasesURL = "https://api.github.com/repos/BishopFox/sliver/releases"
)
var (
@@ -47,22 +55,111 @@ var (
CompiledAt string
)
func init() {
if GithubReleasesURL == "" {
GithubReleasesURL = defaultReleasesURL
}
applyBuildInfo()
}
func applyBuildInfo() {
info, ok := debug.ReadBuildInfo()
if !ok || info == nil {
ensureDefaults()
return
}
if GoVersion == "" {
if info.GoVersion != "" {
GoVersion = info.GoVersion
} else {
GoVersion = runtime.Version()
}
}
if Version == "" {
Version = normalizeVersion(info.Main.Version)
}
settings := buildSettings(info.Settings)
if GitCommit == "" {
GitCommit = settings["vcs.revision"]
}
if GitDirty == "" && settings["vcs.modified"] == "true" {
GitDirty = "Dirty"
}
if CompiledAt == "" {
if vcsTime := settings["vcs.time"]; vcsTime != "" {
if compiled, err := time.Parse(time.RFC3339, vcsTime); err == nil {
CompiledAt = strconv.FormatInt(compiled.Unix(), 10)
}
}
}
ensureDefaults()
}
func ensureDefaults() {
if Version == "" {
Version = defaultVersion
}
if GoVersion == "" {
GoVersion = runtime.Version()
}
}
func buildSettings(settings []debug.BuildSetting) map[string]string {
values := make(map[string]string, len(settings))
for _, setting := range settings {
values[setting.Key] = setting.Value
}
return values
}
func normalizeVersion(version string) string {
if version == "" || version == "(devel)" {
return defaultVersion
}
if canonical := canonicalSemver(version); canonical != "" {
return canonical
}
return version
}
func canonicalSemver(version string) string {
version = strings.TrimSpace(version)
if version == "" || version == "(devel)" {
return ""
}
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
return semver.Canonical(version)
}
// SemanticVersion - Get the structured sematic version
func SemanticVersion() []int {
semVer := []int{}
version := Version
if strings.HasPrefix(version, "v") {
version = version[1:]
semVer := make([]int, 3)
canonical := canonicalSemver(Version)
if canonical == "" {
return semVer
}
for _, part := range strings.Split(version, ".") {
number, _ := strconv.Atoi(part)
semVer = append(semVer, number)
base := strings.TrimPrefix(canonical, "v")
if idx := strings.Index(base, "-"); idx != -1 {
base = base[:idx]
}
parts := strings.Split(base, ".")
for i := 0; i < 3 && i < len(parts); i++ {
semVer[i], _ = strconv.Atoi(parts[i])
}
return semVer
}
// Compiled - Get time this binary was compiled
func Compiled() (time.Time, error) {
if CompiledAt == "" {
return time.Unix(0, 0), errors.New("compiled time not available")
}
compiled, err := strconv.ParseInt(CompiledAt, 10, 64)
if err != nil {
return time.Unix(0, 0), err
@@ -74,7 +171,7 @@ func Compiled() (time.Time, error) {
func FullVersion() string {
ver := fmt.Sprintf("%s", Version)
compiled, err := Compiled()
if err != nil {
if err == nil {
ver += fmt.Sprintf(" - Compiled %s", compiled.String())
}
if GitCommit != "" {
+12 -33
View File
@@ -6,9 +6,9 @@ import (
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
const (
@@ -90,42 +90,21 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
return nil, err
}
mySemVer := SemanticVersion()
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range *releases {
if release.Prerelease && !prereleases {
continue
}
releaseSemVer := parseGitTag(release.TagName)
for index, myVer := range mySemVer {
if index < len(releaseSemVer) {
if releaseSemVer[index] == myVer {
continue
}
if releaseSemVer[index] < myVer {
break
}
if myVer < releaseSemVer[index] {
return release, nil
}
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
}
// parseGitTag - Get the structured sematic version
func parseGitTag(tag string) []int {
semVer := []int{}
version := tag
if strings.Contains(version, "-") {
version = strings.Split(version, "-")[0]
}
if strings.HasPrefix(version, "v") {
version = version[1:]
}
for _, part := range strings.Split(version, ".") {
number, _ := strconv.Atoi(part)
semVer = append(semVer, number)
}
return semVer
}
+1115 -1170
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -26,7 +26,7 @@
"framer-motion": "^12.23.22",
"fuse.js": "^7.1.0",
"lunr": "^2.3.9",
"next": "^15.5.7",
"next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+4 -4
View File
@@ -1,10 +1,10 @@
The armory is the Sliver Alias and Extension package manager (introduced in Sliver v1.5), it allows you to automatically install various 3rd party tools such as BOFs and .NET tooling.
The armory is the Sliver Alias and Extension package manager. It allows you to automatically install various third-party tools such as BOFs and .NET tooling.
The armory downloads packages from `github.com` and `api.github.com` so you'll need an internet connection in order for the command to work. The command does support proxies (see `--help`) and after an alias or extension is installed an internet connection is not required to execute the alias/extension.
Aliases and extensions are installed on the "sliver client"-side, and thus are not shared among operators in [multiplayer mode](/docs?name=Multi-player+Mode).
As of v1.5.14 you can also use `armory install all` to install _everything_ if you really want to.
You can also use `armory install all` to install _everything_ if you really want to.
## The Official Armory
@@ -40,10 +40,10 @@ sliver > armory update
#### Removing Packages
You remove packages installed from the `armory` using the `aliases rm` and `extensions rm` commands depending on if the package is an alias or an extension. You can list installed aliases and extensions by running `aliases` and `extensions` respectfully.
You remove packages installed from the `armory` using the `aliases rm` and `extensions rm` commands depending on if the package is an alias or an extension. You can list installed aliases and extensions by running `aliases` and `extensions` respectively.
Installed alias and extension files are stored in `~/.sliver-client/` by default if you want to manually remove a package simply delete its corresponding directory and restart the client.
## Private Armories
Sliver has experimental support for self-hosted private armories, but I've not gotten around to testing & writing the documentation for these, so you'll have to read thru the source code to figure out how they work for now. I'll eventually update this documentation and release a reference implementation. If you do play around with this, just know the design is subject to change before becoming a real feature.
Sliver has experimental support for self-hosted private armories, but I have not gotten around to testing and writing the documentation for these, so you'll have to read through the source code to figure out how they work for now. I'll eventually update this documentation and release a reference implementation. If you do play around with this, just know the design is subject to change before becoming a real feature.
@@ -1,4 +1,4 @@
Sliver v1.5 and later support the loading and execution of BOFs and COFFs, generally no code changes are needed to use BOFs with a Sliver implant. However, you may need to define a manifest file so that Sliver is aware of BOF arguments and their types.
Sliver supports the loading and execution of BOFs and COFFs, generally no code changes are needed to use BOFs with a Sliver implant. However, you may need to define a manifest file so that Sliver is aware of BOF arguments and their types.
### BOF Extensions
@@ -37,9 +37,9 @@ Flags:
### Converting BOFs to Sliver
Converting existing BOFs to work with Sliver is usually pretty easy, and shouldn't require any code changes. You'll need to define an `extension.json` though based on what arguments/etc. the BOF accepts.
Converting existing BOFs to work with Sliver is usually pretty easy, and shouldn't require any code changes. You'll need to define an `extension.json`, though, based on what arguments/etc. the BOF accepts.
To determine the arguments a BOF accepts and their types, you'll need to read `.cna` script that accompanies a given BOF. For example, the [CredMan](https://github.com/sliverarmory/CredManBOF/blob/main/CredMan.cna) `.cna` script is show below:
To determine the arguments a BOF accepts and their types, you'll need to read the `.cna` script that accompanies a given BOF. For example, the [CredMan](https://github.com/sliverarmory/CredManBOF/blob/main/CredMan.cna) `.cna` script is shown below:
```
alias CredMan {
@@ -29,6 +29,5 @@ generate --http http://example.com?driver=wininet
- `retry-count` - Upon query failure, retry this many times before failing.
- `workers-per-resolver` - Number of worker goroutines per functional DNS resolver.
- `max-errors` - Max number of query errors before failing (integer parsed by `strconv.Atoi`).
- `force-base32` - Always use Base 32 encoding.
- `force-resolv-conf` - Force the use of a provided `resolv.conf`. Note that you'll need to URL encode the newlines/etc of the file contents into the parameter.
- `resolvers` - Force the use of specific DNS resolvers. You can supply more than one by separating them with `+`, i.e. `...?resolvers=1.1.1.1+9.9.9.9`
@@ -1,9 +1,11 @@
You'll want to compile from a MacOS or Linux machine, compiling from native Windows in theory is possible, but none of the asset scripts are designed to run on Windows, you can cross-compile the Windows server/client binaries from a better operating system like Linux or MacOS. If you only have a Windows machine see "Windows Builds" below (TL;DR use WSL).
You'll want to compile from a MacOS or Linux machine. Compiling from native Windows is possible in theory, but none of the asset scripts are designed to run on Windows. You can cross-compile the Windows server/client binaries from a better operating system like Linux or MacOS. If you only have a Windows machine, see "Windows Builds" below (TL;DR: use WSL).
# Sliver v1.6.x
- Go v1.21 or later
- `make`, `sed`, `tar`, `curl`, `zip`, `cut` commands; most of these are installed by default but you may need to install `make`, `curl`, and `zip` depending on your distribution. On MacOS you may need to install XCode and accompanying cli tools.
To compile from source you'll need:
- Go v1.25 or later
- `make` (on MacOS you may need to install XCode and accompanying cli tools)
```asciinema
{"src": "/asciinema/compile-from-source.cast", "cols": "132"}
@@ -26,6 +28,16 @@ $ make
This will create `sliver-server` and `sliver-client` binaries.
Sliver embeds its own copy of the Go compiler and a few internal tools. The first time you run `make`, a bash script will download these assets to your local system. This means the first build will take longer than subsequent builds, especially if your internet connection is slow.
### Cross-compile to Specific Platforms
You can also specify a target platform for the `make` file:
```
$ GOOS=windows GOARCH=amd64 make
```
### Docker Build
There are a few Docker targets available depending on your needs:
@@ -46,11 +58,15 @@ docker build --target production -t sliver .
{"src": "/asciinema/sliver-docker-production.cast", "cols": "132"}
```
The Docker build includes mingw and Metasploit, so it can take a while to build from scratch but Docker should cache the layers effectively. Sliver will also run it's unit tests as part of the build, and that takes a few minutes too.
The Docker build includes mingw and Metasploit, so it can take a while to build from scratch but Docker should cache the layers effectively. Sliver will also run its unit tests as part of the build, and that takes a few minutes too.
### Windows Builds
If all you have is a Windows machine, the easiest way to build Sliver is using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and following the Linux/cross-compile instructions above. To cross-compile a native Windows binary use `make windows-amd64` and copy it to your Windows file system (i.e. `/mnt/c/Users/foo/Desktop`) and run it using a terminal that supports ANSI sequences such as the [Windows Terminal](https://github.com/microsoft/terminal).
# Developers
If you want to modify any of the `.proto` files you'll need to setup a few additional tools to regenerate the `.pb.go` files.
If you want to modify any of the `.proto` files you'll need to set up a few additional tools to regenerate the `.pb.go` files.
- Protoc libprotoc v26.1 or later
- Protoc-gen-go v1.27.1
@@ -62,7 +78,7 @@ First install your platform's version of `protoc` v3.19.4 or later:
https://github.com/protocolbuffers/protobuf/releases/latest
Ensure that correct `protoc` version is on your `$PATH`, you can check with a simple `protoc --version`
Ensure that the correct `protoc` version is on your `$PATH`, you can check with a simple `protoc --version`
#### `protoc-gen-go` `protoc-gen-go-grpc`
@@ -73,71 +89,8 @@ go install google.golang.org/protobuf/cmd/[email protected]
go install google.golang.org/grpc/cmd/[email protected]
```
Ensure that these are both on your `$PATH` after running the commands, if not you probably need to add `$GOPATH/bin` to your `$PATH`. To regenerate the Protobuf and gRPC files run:
Ensure that these are both on your `$PATH` after running the commands; if not, you probably need to add `$GOPATH/bin` to your `$PATH`. To regenerate the Protobuf and gRPC files run:
```
$ make pb
```
# Sliver v1.5.x
- When compiling v1.5.x use Go v1.20.7 you can check your version using `go version`. You can use the [Go Version Manager](https://github.com/moovweb/gvm) to install and manage multiple versions of Go.
- `make`, `sed`, `tar`, `curl`, `zip`, `cut` commands; most of these are installed by default but you may need to install `make`, `curl`, and `zip` depending on your distribution. On MacOS you may need to install XCode and accompanying cli tools.
### Compiling
First git clone the repository:
```
$ git clone https://github.com/BishopFox/sliver.git
$ cd sliver
```
The `master` branch will contain the latest Sliver features, however only release version of Sliver are recommended for production use. It is strongly recommended to checkout the latest tagged release branch when compiling from source unless you're a developer:
```
$ git checkout tags/v1.5.42
```
Sliver embeds its own copy of the Go compiler and a few internal tools, the first time your run `make` a bash script will run to download these assets to your local system. This means the first build will take much longer than subsequent builds, especially if your internet connection is slow.
By default `make` will build whatever platform you're currently running on:
```
$ make
```
**IMPORTANT:** The Sliver Makefile requires version information from the git repository, so you must `git clone` the repository. Using GitHub's "download zip" feature may omit the `.git` directory and result in broken builds.
This will create `sliver-server` and `sliver-client` binaries.
### Cross-compile to Specific Platforms
You can also specify a target platform for the `make` file, though you may need cross-compilers (see below):
```
$ make macos
$ make macos-arm64
$ make linux
$ make linux-arm64
$ make windows-amd64
```
### Docker Build
The Docker builds are mostly designed for running unit tests, but can be useful if you want a "just do everything" build:
```
docker build -t sliver .
```
The Docker build includes mingw and Metasploit, so it can take a while to build from scratch but Docker should cache the layers effectively. Sliver will also run it's unit tests as part of the build, and that takes a few minutes too.
### Windows Builds
**NOTE:** In versions of Sliver prior to 1.6.0 you will need mingw to cross-compile Windows builds:
- Kali/Ubuntu/Debian `sudo apt install mingw-w64`
- MacOS `brew install mingw-w64`
If all you have is a Windows machine, the easiest way to build Sliver is using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and following the Linux/cross-compile instructions above. To cross-compile a native Windows binary use `make windows-amd64` and copy it to your Windows file system (i.e. `/mnt/c/Users/foo/Desktop`) and run it using a terminal that supports ANSI sequences such as the [Windows Terminal](https://github.com/microsoft/terminal).
@@ -1,6 +1,6 @@
### General Server Configuration
Starting in version 1.0.0 the Sliver server has a configuration file located the `configs` sub-directory of the [`SLIVER_ROOT_DIR`](/docs?name=Environment+Variables), by default this will be `~/.sliver/configs/server.json`. If no configuration file exists, a default configuration will be generated and written to disk on startup. The default configuration is shown below:
The Sliver server has a configuration file located in the `configs` sub-directory of the [`SLIVER_ROOT_DIR`](/docs?name=Environment+Variables); by default this is `~/.sliver/configs/server.json`. If no configuration file exists, a default configuration will be generated and written to disk on startup. The default configuration is shown below:
#### Default Server Config
@@ -24,15 +24,15 @@ Starting in version 1.0.0 the Sliver server has a configuration file located the
- `daemon_mode` - Enable [daemon mode](/docs?name=Daemon+Mode)
- `daemon` - An object containing options related to `daemon_mode`, these values are only used when `daemon_mode` is set to `true`.
- `host` - What network interface to bind the `daemon_mode` client listener to. By default this is an empty string, which indicates binding to all interfaces.
- `port` - TCP port to bind the `deamon_mode` client listener to.
- `logs` - An object containing configure options for server logs
- `level` - The `logrus` logging level (1 - 5), default this is set to `4`, which is the`INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
- `port` - TCP port to bind the `daemon_mode` client listener to.
- `logs` - An object containing configuration options for server logs
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
- `grpc_unary_payloads` - Log gRPC unary payloads
- `grpc_stream_payloads` - Log gRPC streaming payloads
## Database Configuration
Starting in version v1.1.0 Sliver supports SQL database configurations, by default this configuration file is located at `~/.sliver/configs/database.json`:
Sliver supports SQL database configurations. By default this configuration file is located at `~/.sliver/configs/database.json`:
#### Default Database Configuration
@@ -59,7 +59,7 @@ Starting in version v1.1.0 Sliver supports SQL database configurations, by defau
- `password` - Database password.
- `host` - Database host.
- `port` - Database TCP port.
- `params` - Is a key/value string map that can be used to pass dialect specific options to the DSN/connection string. This can be used for example to enable SSL/TLS on certain database connections.
- `params` - Is a key/value string map that can be used to pass dialect-specific options to the DSN/connection string. This can be used, for example, to enable SSL/TLS on certain database connections.
- `max_idle_conns` - Max idle connections in the database connection pool.
- `max_open_conns` - Max open connections in the database connection pool.
- `log_level` - Configures the log level for database logging, can be set to: `silent`, `error`, `warn`, or `info`. Default level is `warn`, note that setting this option to `info` may result in sensitive information being logged to disk!
@@ -1,24 +1,20 @@
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box, you only need cross-compilers when using `--format shared`.
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you only need cross-compilers when using `--format shared`.
Sliver can tell you which platforms it can likely target based on the server's platform and available cross-compilers by running the `generate info` command in the console.
Sliver v1.5.30 and later also support [External Builders](/docs?name=External+Builders), which can be used to easily cross-compile implants.
Sliver supports [External Builders](/docs?name=External+Builders), which can be used to easily cross-compile implants.
## From Linux to MacOS/Windows
To compile Windows shared library and shellcode implants from Linux, install mingw from your local package manager. Note version 1.6 and later embed a zig cross-compiler out of the box and do not need ming-w64 to produce Windows DLLs:
Sliver embeds a Zig cross-compiler out of the box, so Windows shared library and shellcode implants from Linux do not require mingw-w64.
```
sudo apt install mingw-w64
```
To compile MacOS shared library implants from Linux, we recommend using https://github.com/tpoechtrager/osxcross by default Sliver will look in `/opt/osxcross` but you can override this via [environment variables](/docs?name=Environment+Variables). If you do not have a MacOS based machine you can use GitHub Actions' MacOS instances to build OSXCross.
To compile MacOS shared library implants from Linux, we recommend using https://github.com/tpoechtrager/osxcross by default Sliver will look in `/opt/osxcross` but you can override this via [environment variables](/docs?name=Environment+Variables). If you do not have a MacOS-based machine you can use GitHub Actions' MacOS instances to build OSXCross.
**NOTE:** Sliver expects the root of the osxcross git repo to be located at `/opt/osxcross` and the actual binaries in `/opt/osxcross/target/bin`.
An example deployment is shown below, you have to procure the `MacOSX123.sdk.tar.xz` yourself due to license restrictions (see the OSXCross GitHub for more details). It is recommended to use the llvm-based version (i.e. the `2.0-llvm-based` branch) of osxcross:
For a MacOS machine, if you don't have one handy I recommend using a MacOS Github Action runner:
For a MacOS machine, if you don't have one handy I recommend using a MacOS GitHub Actions runner:
```shell
git clone https://github.com/tpoechtrager/osxcross.git
@@ -37,26 +33,13 @@ curl -o ./tarballs/MacOSX123.sdk.tar.xz 'https://example.com/MacOSX123.sdk.tar.x
TARGET_DIR=/opt/osxcross ENABLE_ARCHS="arm64 x86_64" ./build.sh
```
Sliver automatically looks in the default paths for these cross compilers, once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
Sliver automatically looks in the default paths for these cross-compilers; once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross-compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
## From MacOS to Linux/Windows
Note version 1.6 and later embed a zig cross-compiler out of the box and do not need ming-w64 or musl-cross to produce Windows DLLs and Linux shared objects.
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects do not require mingw-w64 or musl-cross.
To compile Windows shared library and shellcode implants from MacOS install mingw from brew:
```
brew install mingw-w64
```
For Linux, we recommend `musl-cross` to target 64-bit Linux, which can be installed via brew:
```
brew install FiloSottile/musl-cross/musl-cross
brew install mingw-w64
```
I'm not aware of any good options to target 32-bit Linux from MacOS. Sliver automatically looks in the default paths for these cross compilers, once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
However, we're not aware of any good options to target 32-bit Linux from MacOS. Sliver automatically looks in the default paths for these cross-compilers; once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross-compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
## From Windows to MacOS/Linux
+6 -6
View File
@@ -1,17 +1,17 @@
Cursed is a Chrome/Chromium/Edge/Electron post-exploitation tool kit introduced in Sliver v1.5.25, which integrates with [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome) (originally [Sliver Overlord](https://github.com/BishopFox/sliver-overlord)). It can automatically find existing Chrome Extensions with the required permissions for [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome) and remotely inject it onto the target system, or you can start an interactive REPL to inject arbitrary code into any Chrome/Chromium/Edge/Electron context.
Cursed is a Chrome/Chromium/Edge/Electron post-exploitation toolkit that integrates with [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome) (originally [Sliver Overlord](https://github.com/BishopFox/sliver-overlord)). It can automatically find existing Chrome extensions with the required permissions for [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome) and remotely inject it onto the target system, or you can start an interactive REPL to inject arbitrary code into any Chrome/Chromium/Edge/Electron context.
Since web requests and other activity originate from the target machine/browser instance, Cursed Chrome and the Cursed tool kit are excellent options for bypassing U2F/Webauthn, hardware attestation, and geo-IP restrictions in web applications.
Since web requests and other activity originate from the target machine/browser instance, Cursed Chrome and the Cursed toolkit are excellent options for bypassing U2F/Webauthn, hardware attestation, and geo-IP restrictions in web applications.
## Cursed Chrome
The `cursed chrome` command can be used to restart a remote system's Chrome browser with remote debugging enabled. If no payload is specified using `--payload` the command will simply restart Chrome with remote debugging enabled, you can then use `cursed console` to interact with any debug target.
The `cursed chrome` command can be used to restart a remote system's Chrome browser with remote debugging enabled. If no payload is specified using `--payload` the command will simply restart Chrome with remote debugging enabled; you can then use `cursed console` to interact with any debug target.
If a payload is specified, the command will restart Chrome with remote debugging, enumerate installed browser extensions, determine if any extension has the required permissions for [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome), and inject the payload into the extension's execution context.
So a typical workflow looks like:
1. Setup [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome)
2. Pop Sliver session, or go interactive from a beacon
1. Set up [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome)
2. Pop a Sliver session or go interactive from a beacon
3. `cursed chrome --payload background.js`
4. Upstream browser to CursedChrome proxy
5. Enjoy!
@@ -30,7 +30,7 @@ The `cursed console` command can be used to start an interactive REPL with any c
## Cursed Cookies
Starting in v1.5.28 the `cursed cookies` command can be used to dump a remote cursed process' cookies to a local file (newline delimited json), for example:
The `cursed cookies` command can be used to dump a remote cursed process's cookies to a local file (newline-delimited JSON), for example:
```
[server] sliver (CHRONIC_GOAT) > cursed chrome
Loaded 100 of 797 files, more files were not shown because too many files have changed in this diff. Show more